views:

310

answers:

3

Hi all,

I think this question is really about my understanding of Garbage collection and variable references. But I will go ahead and throw out some code for you to look at.

// Please note do not use this code for async sockets, just to highlight my question

// SocketTransport
// This is a simple wrapper class that is used as the 'state' object
// when performing Async Socket Reads/Writes
public class SocketTransport
{
    public Socket Socket;
    public byte[] Buffer;
    public SocketTransport(Socket socket, byte[] buffer)
    {
        this.Socket = socket;
        this.Buffer = buffer;
    }
}

// Entry point - creates a SocketTransport, then passes it as the state
// object when Asyncly reading from the socket.
public void ReadOne(Socket socket)
{
    SocketTransport socketTransport_One =
        new SocketTransport(socket, new byte[10]);

    socketTransport_One.Socket.BeginRecieve
        (
        socketTransport_One.Buffer,    // Buffer to store data
        0,                             // Buffer offset
        10,                            // Read Length
        SocketFlags.None               // SocketFlags
        new AsyncCallback(OnReadOne),  // Callback when BeginRead completes
        socketTransport_One            // 'state' object to pass to Callback.
        );
}

public void OnReadOne(IAsyncResult ar)
{
    SocketTransport socketTransport_One = ar.asyncState as SocketTransport;
    ProcessReadOneBuffer(socketTransport_One.Buffer);  // Do processing

    // New Read
    // Create another! SocketTransport (what happens to first one?)
    SocketTransport socketTransport_Two =
        new SocketTransport(socket, new byte[10]);

    socketTransport_Two.Socket.BeginRecieve
        (
        socketTransport_One.Buffer,
        0,
        10,
        SocketFlags.None
        new AsyncCallback(OnReadTwo),
        socketTransport_Two
        );
}

public void OnReadTwo(IAsyncResult ar)
{
    SocketTransport socketTransport_Two = ar.asyncState as SocketTransport;
    ..............

So my question is:

  • The first SocketTransport to be created (socketTransport_One) has a strong reference to a Socket object (lets call is ~SocketA~).

  • Once the async read is completed, a new SocketTransport object is created (socketTransport_Two) also with a strong reference to ~SocketA~.

    Q1. Will socketTransport_One be collected by the garbage collector when method OnReadOne exits? Even though it still contains a strong reference to ~SocketA~

Thanks all!

+5  A: 

In your example, socketTransport_One should be garbage-collected, because no other objects have a strong reference to it. Just because it has a strong reference to another object doesn't mean it's ineligible for garbage collection.

Adam Maras
Hi Adam, thanks for the help. If you are right and socketTransport_One is garbage collected - will it's Socket be Dispose()d? Even though it is being used in socketTransport_Two?
divinci
No, because `socketTransport_Two` will now have a strong reference to the socket. It won't be garbage collected because it's still being referenced.
Adam Maras
@divinci The only way the Socket will be Disposed when the SocketTransport is garbage collected (which as Brian mentioned in another answer, may not be happening without a call to EndInvoke) is if the SocketTransport class has a finalizer that explicitly calls Dispose on the Socket it owns.
Gideon Engelberth
+3  A: 

For more information on how the garbage collector works see http://msdn.microsoft.com/en-us/library/ee787088.aspx#what_happens_during_a_garbage_collection

Brian Sandlin
+2  A: 

Adam is right, socketTransport_One is eligible for garbage collection as soon as OnReadOne() exits. However, eligibility doesn't mean that garbage collection will actually happen right then.

Brian is right too, in that you should always call EndReceive (the EndX pair to any BeginX method in general). This is according to MSDN. However, in the current implementation, you won't leak any resources even if you fail to call EndReceive. The AsyncState is released as soon as your callback finishes. But then again, you should not rely on this.

@Brian: About a Socket left without references when it still has work to do: it too is going to be garbage collected. Its Dispose() method could wait for pending operations to finish, but I think right now this feature is disabled. So you won't leak anything here either.

I've put together a little toy to play with, I hope it helps to clear things up even more:

using System;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;

namespace ConsoleApplication95
{
    class MySocket : Socket
    {
        readonly string name;
        public string Name
        {
            get { return name; }
        }

        public MySocket(string newName, AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
            : base(addressFamily, socketType, protocolType)
        {
            name = newName;
        }

        protected override void Dispose(bool disposing)
        {
            Console.WriteLine("Socket " + Name + " disposing");
            base.Dispose(disposing);
        }
    }

    class Program
    {
        static TcpListener listener;

        static void Main(string[] args)
        {
            listener = new TcpListener(IPAddress.Any, 2055);
            listener.Start();

            Thread t = new Thread(TcpService);
            t.Start();

            Console.WriteLine("TCP server started, listening to port 2055");

            SocketTransport tr = new SocketTransport("1", new MySocket("1", AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), new byte[64]);
            tr.Socket.Connect(IPAddress.Loopback, 2055);
            tr.Socket.BeginReceive(tr.Buffer, 0, tr.Buffer.Length, SocketFlags.None, OnReadOne, tr);
            tr = null;

            Console.WriteLine("Press enter to trigger GC");
            Console.ReadLine();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Console.WriteLine("Press enter to exit");
            Console.ReadLine();
        }

        public class SocketTransport : IDisposable
        {
            public Socket Socket;
            public byte[] Buffer;
            public string Name;
            public SocketTransport(string name, Socket socket, byte[] buffer)
            {
                Name = name;
                Socket = socket;
                Buffer = buffer;
            }

            public void Dispose()
            {
                Console.WriteLine("SocketTransport " + Name + " disposing");
            }

            ~SocketTransport()
            {
                Dispose();
            }
        }

        public static void OnReadOne(IAsyncResult ar)
        {
            SocketTransport tr = ar.AsyncState as SocketTransport;
            string message = Encoding.ASCII.GetString(tr.Buffer);
            Console.WriteLine("OnReadOne: " + message);
            Socket socket = tr.Socket;

            ar = null;
            tr = null;
            // SocketTransport 1 would become eligible for garbage collection here
            // if the caller of OnReadOne didn't hold a reference as a local variable.
            // 
            // As soon as we exit from this method, our caller exits too
            // and the local reference will be no more and SocketTransport 1
            // can be garbage collected. It doesn't matter whether we
            // call EndReceive or not, as illustrated with the triggered GC
            // in OnReadTwo() and the one after pressing enter in Main.

            SocketTransport tr2 = new SocketTransport("2", socket, new byte[64]);
            tr2.Socket.BeginReceive(tr2.Buffer, 0, tr2.Buffer.Length, SocketFlags.None, OnReadTwo, tr2);
        }

        public static void OnReadTwo(IAsyncResult ar)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();

            SocketTransport tr = ar.AsyncState as SocketTransport;
            tr.Socket.EndReceive(ar);
            string message = Encoding.ASCII.GetString(tr.Buffer);
            Console.WriteLine("OnReadTwo: " + message);
        }

        public static void TcpService()
        {
            using (Socket socket = listener.AcceptSocket())
            {
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 10000);

                Console.WriteLine("Connected: {0}", socket.RemoteEndPoint);
                try
                {
                    socket.NoDelay = true;
                    socket.Send(Encoding.ASCII.GetBytes("message 1"));
                    Thread.Sleep(100);
                    socket.Send(Encoding.ASCII.GetBytes("message 2"));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }

                Console.WriteLine("Disconnecting: {0}", socket.RemoteEndPoint);
            }
        }
    }
}
kicsit