一些异步套接字代码 - 如何处理垃圾回收?

4

我认为这个问题实际上与我对垃圾回收变量引用的理解有关。但是我会提供一些代码让你看看。

// 请注意,不要将此代码用于异步套接字,只是为了强调我的问题

// 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;
    ..............

所以我的问题是:
  • 第一个创建的SocketTransport(socketTransport_One)对一个Socket对象有强引用(我们称其为~SocketA~)。

  • 一旦异步读取完成,将创建一个新的SocketTransport对象(socketTransport_Two),该对象也具有对~SocketA~的强引用。

    Q1. 当OnReadOne方法退出时,socketTransport_One会被垃圾回收器回收吗?即使它仍然包含对~SocketA~的强引用。

感谢大家!
3个回答

5
在你的示例中,socketTransport_One应该被垃圾回收,因为没有其他对象对它有强引用。仅仅因为它对另一个对象有强引用并不意味着它不能被垃圾回收。

嗨,亚当,感谢你的帮助。如果你是对的,socketTransport_One被垃圾回收了 - 它的Socket会被Dispose()吗?即使它正在socketTransport_Two中使用? - divinci
不行,因为socketTransport_Two现在将对套接字有一个强引用。它不会被垃圾回收,因为它仍然被引用着。 - Adam Maras
@divinci 唯一的方法是当 SocketTransport 被垃圾回收时(正如 Brian 在另一个答案中提到的,如果没有调用 EndInvoke,可能不会发生),Socket 才会被处理,这只有在 SocketTransport 类具有显式调用其所拥有的 Socket 的 Dispose 方法的终结器时才会发生。 - Gideon Engelberth

3

2

Adam是正确的,一旦OnReadOne()退出,socketTransport_One就有资格进行垃圾回收。但是,资格并不意味着垃圾回收会立即发生。

Brian也是正确的,在一般情况下,你应该始终调用EndX方法(BeginX方法的成对方法),包括EndReceive。这是根据MSDN的建议。然而,在当前实现中,即使你未能调用EndReceive,也不会泄漏任何资源。异步状态会在回调完成后立即释放。但是,你仍然不应该依赖于此。

@Brian:关于Socket在仍有工作要做时被留下的问题:它也将被垃圾回收。它的Dispose()方法可能会等待挂起的操作完成,但我认为目前此功能已禁用。因此,这里也不会泄漏任何东西。

我编写了一个小玩具来测试,希望它能更清楚地解释问题:

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);
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接