C# 异步 TCP 和其引用实例的垃圾回收闭包?

3

如果在异步TCP或其他EAP模式中,成功处理程序具有对this的引用,例如this.state,理论上,当前实例应该有一个引用,因为this被闭包自然保持在某个生成的对象范围内。因此,即使创建实例的作用域已经执行完毕,实例本身也不应该被垃圾回收?

我的代码类似于以下代码:

 public class ATcpClient
    {
        private ATcpState state = null;

        private void Receive()
        {
            // create the callback here, in order to use in dynamic
            AsyncCallback ReceiveCallback = delegate(IAsyncResult ar)
                {
                    try
                    {
                        // Read data from the remote device.
                        this.state.BytesReceived = this.state.Socket.EndReceive(ar);

                    }
                    catch (Exception e)
                    {
                        // ...

                    }
                };

            try
            {
                    this.state.Socket.BeginReceive(this.state.Buffer, 0, this.state.BufferSize, 0,
                        ReceiveCallback, null);
            }
            catch (Exception e)
            {
                // ...
                // ...
            }
        }
}

执行它的代码可能如下所示:

public void DoExecuteCode()
{
    new ATcpClient().Receive();
}

实例被垃圾回收导致整个Receive()失败了吗?
1个回答

2

这取决于编译器的智能程度。

在你的情况下,是的,只要委托存在,this 就一定会被保留。

现在来看一个明显不会保留 this 的情况:

    private void Receive()
    {
        ATcpState state = this.state;
        // create the callback here, in order to use in dynamic
        AsyncCallback ReceiveCallback = delegate(IAsyncResult ar)
            {
                try
                {
                    // Read data from the remote device.
                    state.BytesReceived = state.Socket.EndReceive(ar);

                }
                catch (Exception e)
                {
                    // ...

                }
            };

        try
        {
                state.Socket.BeginReceive(state.Buffer, 0, state.BufferSize, 0,
                    ReceiveCallback, null);
        }
        catch (Exception e)
        {
            // ...
            // ...
        }
    }

还有一种情况是编译器优化可能会影响收集行为:

    private readonly ATcpState state = new ATcpState();
    private void Receive()
    {
        // create the callback here, in order to use in dynamic
        AsyncCallback ReceiveCallback = delegate(IAsyncResult ar)
            {
                try
                {
                    // Read data from the remote device.
                    state.BytesReceived = state.Socket.EndReceive(ar);

                }
                catch (Exception e)
                {
                    // ...

                }
            };

        try
        {
                state.Socket.BeginReceive(state.Buffer, 0, state.BufferSize, 0,
                    ReceiveCallback, null);
        }
        catch (Exception e)
        {
            // ...
            // ...
        }
    }

唯一剩下的问题是,该委托的生命周期是多久?挂起的操作是否是根操作,还是我们只是在委托、可能是thisstatestate.Socket和操作之间形成了循环引用?如果没有一个从根可达到这些对象,则整个组合可以被终结(这将关闭套接字并取消操作)。
对于某些使用BeginReceive/EndReceive模式的对象,至少操作不是根操作
看起来对于您的情况(Socket.BeginReceive),根是在System.Net.Sockets.BaseOverlappedAsyncResult.PinUnmanagedObjects内创建的。

实际上我的ATcpClient在构造函数中需要一个ATcpState,而整个东西比我列出的简化代码更复杂。根据您的评论,我认为它是安全的,并且编译器不会那么“聪明”,希望如此。今晚会进行一些测试...谢谢。 - ccppjava
@ccppjava:您可能只看了我的回答的一部分,请务必查看更新内容。仅仅拥有一个对象的引用是不足以防止垃圾收集的,它必须从“GC根”可达(例如调用堆栈中函数的局部变量、静态成员变量或GCHandle)。 - Ben Voigt
谢谢,已经看到你的更新了,我认为你是正确的。虽然这意味着会有一个更复杂的清理线程在运行,但更安全。我同意整个“可能循环引用”的参考事情可以作为整体进行垃圾回收。 - ccppjava
2
@ccppjava:我深入研究了Socket代码,你没问题,创建了一个GCHandle - Ben Voigt
@Ben,很酷,这意味着我不需要自己管理一些静态字典并定期检查资源释放 :) 等我完成目前需要做的事情后,还会进行一些实验...谢谢... - ccppjava
@Ben,我已经测试了使用多个线程和多个对象。观察使用~ATcpClient() {}。如果对象仍在执行某些操作,则它将不会被垃圾收集,这对我来说很好 :) 谢谢。 - ccppjava

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