Some Async Socket Code - Help with Garbage Collection?
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 objec开发者_C百科t 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!
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.
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
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);
}
}
}
}
精彩评论