2

我正在为一个打开套接字,发出请求并监听响应的类使用一些代码(不是我的,我急于添加,我一点也不信任),它以我可以的方式抛出异常在 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;
            }
        }
    }
}
4

3 回答 3

8

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

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

(我知道你一开始就澄清这不是你的代码 - 我只是想我会解释为什么它有问题。如果你已经知道一些/所有这些,请道歉。)

于 2009-01-08T10:29:23.007 回答
4

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

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

我强烈建议您阅读这篇非常重要的 Microsoft 文章,详细了解垃圾收集的工作原理。此外,这是 MSDN 关于实现 Finalize 和 Dispose 以清理非托管资源的参考,请仔细查看底部的建议。

简而言之:

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

新信息:这看起来像我实际上有两个问题,但线程似乎是相当有毒的。

从上面的 MSDN 链接:

“ThreadAbortException 是一个可以被捕获的特殊异常,但它会在 catch 块结束时再次自动引发。”

Some very interesting community content also at that link including "Thread.Abort is a Sign of a Poorly Designed Program".

So at least I have some ammo to get this changed now :)

于 2009-01-08T17:24:19.817 回答