RE

Hi all, I can't really summarize my questions into the subject as it's quite complicated.

This is my situation. I'm going to have hundreds of objects of the same class (to be concise, hundreds of TCP connections wrapped by the class Connection), none of which needs to be referenced--they work on their own, and will stop when no longer needed. To make the program non-CPU-intensive I used asynchronous I/O, hoping that the methods held by AsyncCallback delegates will keep the objects alive.

I then wrote a tester program which seemed to perfectly support my assumption, as seen below. It starts the listener thread, initiates a TCP connection, and keeps GC'ing every second. As long as the connection is there (as well as the listener), the Connection object won't be GC'd.


using System; using System.Net; using System.Net.Sockets; using System.Threading; namespace KeepAliveByThreadPool { class Program { static AutoResetEvent listenerStarted = new AutoResetEvent(false); static volatile bool cannotStart = false; static AutoResetEvent requestedToStopListener = new AutoResetEvent(false); static void Main(string[] args) { // Starts the listener new Thread(Listener).Start(); listenerStarted.WaitOne(); if (cannotStart) return; // Starts the connection Console.WriteLine("Press any key to establish a connection."); Console.ReadKey(); TcpClient tc = new TcpClient("localhost", 8765); // Starts the timer who asks the GC to collect the garbage every second. Timer t = new Timer(delegate { Console.Write("gc..."); GC.Collect(); }, null, 1000, 1000); // Stops the connection Console.WriteLine("\r\nPress any key to stop the connection.\r\n"); Console.ReadKey(); tc.GetStream().Close(); requestedToStopListener.Set(); Console.WriteLine("\r\n\r\nPress any key to end the program.\r\n"); Console.ReadKey(); GC.KeepAlive(t); } static void Listener() { // listens on port 8765 TcpListener tl = new TcpListener(IPAddress.Any, 8765); try { tl.Start(); } catch { cannotStart = true; Console.WriteLine("Listener cannot be started."); Console.ReadKey(); return; } // accepts a socket, wraps it in a Connection object, // starts it, and waits for the end of program. try { listenerStarted.Set(); // nothing is holding the Connection object: new Connection(tl.AcceptSocket()).Start(); requestedToStopListener.WaitOne(); } finally { tl.Stop(); } } class Connection { Socket s; public Connection(Socket s) { Console.WriteLine("\r\nA connection is established."); this.s = s; } ~Connection() { Console.Write("GARBAGE-COLLECTED..."); } public void Start() { s.BeginReceive(new byte[1024], 0, 1024, SocketFlags.None, End, null); } private void End(IAsyncResult ar) { try { s.EndReceive(ar); } catch { } finally { s.Close(); } } } } }

The result looks like this:


Press any key to establish a connection. A connection is established. Press any key to stop the connection. gc...gc...gc...gc...gc...gc...gc...gc...gc...gc...gc...gc...gc...gc...gc...gc... gc...gc...gc...gc... Press any key to end the program. gc...GARBAGE-COLLECTED...gc...gc...gc...gc...gc...gc...gc...

But then when I wanted to prove it further, thus let the listener thread quit as soon as it accepts a socket, I found myself wrong. The code is changed to as follows:


namespace CannotKeepAliveByThreadPool { class Program { static AutoResetEvent listenerStarted = new AutoResetEvent(false); static volatile bool cannotStart = false; static void Main(string[] args) { // listens on port 8765 TcpListener tl = new TcpListener(IPAddress.Any, 8765); // Starts the listener new Thread(Listener).Start(tl); listenerStarted.WaitOne(); if (cannotStart) return; // Starts the connection Console.WriteLine("Press any key to establish a connection."); Console.ReadKey(); TcpClient tc = new TcpClient("localhost", 8765); // Starts the timer who asks the GC to collect the garbage every second. Timer t = new Timer(delegate { Console.Write("gc..."); GC.Collect(); }, null, 1000, 1000); // Stops the connection Console.WriteLine("\r\nPress any key to stop the connection.\r\n"); Console.ReadKey(); tc.GetStream().Close(); tl.Stop(); Console.WriteLine("\r\n\r\nPress any key to end the program.\r\n"); Console.ReadKey(); GC.KeepAlive(t); } static void Listener(object o) { TcpListener tl = (TcpListener)o; try { tl.Start(); } catch { cannotStart = true; Console.WriteLine("Listener cannot be started."); Console.ReadKey(); return; } // accepts a socket, wraps it in a Connection object, // starts it, and EXITS THIS THREAD. listenerStarted.Set(); new Connection(tl.AcceptSocket()).Start(); } class Connection // unchanged ...

And the result is that the Connection object is GC'd at the very first call to GC.Collect():


Press any key to establish a connection. A connection is established. Press any key to stop the connection. gc...GARBAGE-COLLECTED...gc...gc...gc...gc...gc...gc...gc...

Now that it proved that ThreadPool can't keep objects alive, are there any other ways to help me keep the Connections present as long as they're needed And if anyone has any idea, I would also like to know why in the first case the Connection is not GC'd provided that nothing at all is holding it.

Thanks for reading my thread till the end!



Answer this question

RE

  • Ntech

    The easiest solution is to pass your connection object in the last argument to BeginReceive.

    This will keep it from being collected.

    Also which version of .NET are you using


  • Big--Ben

    Hmm... could you please explain more about what can be done in a "single instance of the heap" I'm getting confused.

    <edit> ok... now I understand what you mean; but in the real case I'm gonna have hundreds of connections (and thus hundreds of Connection objects, each holding a Socket instance), and I'm searching for a best way to keep them all from GC'd until the socket is no longer useful. I thought the delegates used in the thread pool will help me keeping the objects alive.

    Anyway, as a fallback method, I think I can keep the listening and accepting thread present--although odd, as long as the thread in which sockets are accepted doesn't end, the Connection instances won't be GC'd.


  • hornerj99

    Hi, thanks for the reply! but your solution doesn't seem to help. I changed the last argument from "null" to "this" in the following line:

    s.BeginReceive(new byte[1024], 0, 1024, SocketFlags.None, End, this);

    and the Connection object is still GC'd at the first timer callback. If I do this:

    // accepts a socket, wraps it in a Connection object,
    // starts it, and EXITS THIS THREAD.
    listenerStarted.Set();
    new Connection(tl.AcceptSocket()).Start();
    Thread.Sleep(1000);

    then the Connection object is GC'd at the second callback--so the state object in the asynchronous call can't keep itself alive either.

    Anyway, I'm using my fallback method described earlier, to "keep the listening and accepting thread present--although odd, as long as the thread in which sockets are accepted doesn't end, the Connection instances won't be GC'd."

    I'm using .NET 2.0 for this project.


  • ThunderChildAU

    Hi Max

    As stated in the thread your two options are to either keep the connection objects alive using references (like storing them in a hashtable) or by keepting the threads that created them alive.  Delegates will not keep them alive.

    Hope that helps

    -Chris


  • Nick752008

    RE: are there any other ways to help me keep the Connections present as long as they're needed

    Try storing the connection instance outside of the worker threads (and hence the pooler).

     



  • Kijju

    So that means I have to use references...

    Referencing to hundreds of Connections isn't easy; the only way I can think of to be appropriate is to use a Hashtable, non-generic (since generic collections don't have intrinsic synchronization), and ask the Connection objects to remove themselves from the Hashtable when no longer needed...

    That's feasible but a little bit redundant and stupid... and that's why I'm asking whether there'll be better solutions.


  • Lesley

    So you'd rather have copies of connection object instances in memory in the hundreds instead (an individual instance of a connection object for each thread) I'd rather have copies of references to a single instance in the heap - it's more efficient and uses less memory. That's not so "stupid" to me!

  • RE