【问题标题】:Problem disposing of socket / finalising twice?处理套接字/完成两次的问题?
【发布时间】:2010-09-30 05:49:17
【问题描述】:

我正在为一个打开套接字、发出请求并侦听响应的类使用一些代码(不是我的,我急于添加,我一点也不信任),这会引发异常在 xunit 中测试时我无法理解的方式。我假设同样的异常发生在“现场”,但该类被单例引用,所以它可能只是被隐藏了。

问题在 xunit 中表现为“System.CannotUnloadAppDomainException: Error while unloading appdomain”,内部异常是在关闭套接字时(基本上)在终结器内抛出“System.ObjectDisposedException”!没有其他对调用 close 和 dispose 的套接字的引用在 Socket 类上受到保护,所以我不清楚如何处理该对象。

此外,如果我只是捕获并吸收 ObjectDisposedException,xunit 在到达关闭侦听器线程的行时终止。

我只是不明白在要求关闭之前如何处理 Socket。

我对套接字的了解只是我发现这个问题后学到的,所以我不知道我是否提供了 SO 可能需要的一切。如果没有,LMK!

public class Foo
{
    private Socket sock = null;
    private Thread tListenerThread = null
    private bool bInitialised;
    private Object InitLock = null;
    private Object DeInitLock = null;

    public Foo()
    {
        bInitialised = false;

        InitLock = new Object();
        DeInitLock = new Object();
    }

    public bool initialise()
    {
        if (null == InitLock)
            return false;

        lock (InitLock)
        {
            if (bInitialised)
                return false;

            sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 8);
            sock.Bind( /*localIpEndPoint*/);
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(mcIP));

            tListenerThread = new Thread(new ThreadStart(listener));
            tListenerThread.Start();

            bInitialised = true;
            return true;
        }
    }

    ~Foo()
    {
        if (bInitialised)
            deInitialise();
    }

    private void deInitialise()
    {
        if (null == DeInitLock)
            return;

        lock (DeInitLock)
        {
            if (bInitialised)
            {
                sock.Shutdown(SocketShutdown.Both); //throws System.ObjectDisposedException
                sock.Close();

                tListenerThread.Abort(); //terminates xunit test!
                tListenerThread = null;

                sock = null;

                bInitialised = false;
            }
        }
    }
}

【问题讨论】:

    标签: c# sockets dispose finalizer xunit


    【解决方案1】:

    如果此对象符合垃圾回收条件并且没有其他对 Socket 的引用,那么 socket 的 终结器可能会在对象的终结器之前运行。我怀疑这就是这里发生的事情。

    在终结器中做这么多工作通常是个坏主意 (IMO)。我完全不记得我上一次实现终结器是什么时候了——如果你实现了 IDisposable,你应该没问题,除非你有对非托管资源的直接引用,这些资源几乎总是采用 IntPtrs 的形式。有序关闭应该是常态——通常只有在程序正在关闭或有人忘记处置实例开始时才会运行终结器。

    (我知道你一开始就澄清了这不是你的代码 - 我只是想我会解释为什么它会出现问题。如果你已经知道部分/全部内容,我深表歉意。)

    【讨论】:

    • 谢谢。鉴于该类在单例中被引用,预计将持续项目的生命周期,终结器 is 在关闭时运行(通过 IIS 或 xunit 的拆卸) - 如果 GC 将拾取套接字和Threads 这个类需要deInit吗?
    • (应该是应用实例的生命周期,而不是项目)
    • 嗯...线程是这里的棘手位。坦率地说,明确地中止线程无论如何都是一件令人讨厌的事情,但您可能需要一些更清洁的方法来让它消失。假设 xunit 卸载 AppDomain,它很可能会被中止,但这值得检查。
    • 我认为必须给它“CannotUnloadAppDomainException”-无论如何我都会尝试一下..这当然不会让同事高兴:)
    【解决方案2】:

    由于垃圾收集器和终结器的工作方式,只有当您的类是非托管资源(例如窗口句柄、GDI 对象、全局句柄)的直接所有者时,才必须使用终结器或任何其他类型的 IntPtr。

    终结器不得尝试释放甚至使用托管资源,否则您将面临调用终结或释放对象的风险。

    我强烈建议您阅读very important Microsoft article,了解有关垃圾收集工作原理的更多详细信息。另外,这是 Implementing Finalize and Dispose to Clean Up Unmanaged Resources 上的 MSDN 参考,请仔细查看底部的建议。

    简而言之:

    • 如果您的对象持有非托管资源,则应实现 IDisposable 并且必须实现 Finalizer。
    • 如果您的对象持有 IDiposable 对象,它还应自行实现 IDisposable 并显式处置该对象。
    • 如果您的对象同时包含非托管和一次性,终结器必须调用两个不同版本的 Dispose,一个释放一次性和非托管,另一个仅非托管。这通常使用由 Dipose() 和 Finalizer() 调用的 Dispose(bool) 函数来完成。
    • Finalizer 绝不能使用除被释放的非托管资源和自身之外的任何其他资源。如果不这样做,将有引用收集或处置的对象的风险,因为对象在最终确定之前会暂时恢复。

    【讨论】:

    • 该建议可以追溯到 .net 1.0;我认为更现代的范例是任何需要终结器的对象都应该关注一种类型的非托管资源(需要终结的实体)的管理,而没有其他。如果一个类需要管理除单一类型的非托管资源之外的任何东西,则每种类型的非托管资源都应包装在其自己的类中(将其转换为托管资源),然后更大的类应该只包含托管资源。
    【解决方案3】:

    新信息:这看起来我实际上有两个问题,但线程一 appears 相当有毒。

    来自上面的 MSDN 链接:

    "ThreadAbortException 是一个特殊的 可以捕获的异常,但它 将在 catch 块的结尾。”

    该链接上还有一些非常有趣的社区内容,包括"Thread.Abort is a Sign of a Poorly Designed Program"

    所以至少我现在有一些弹药可以改变这个:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 2021-11-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多