问题处理插座/完成两次?

时间:2009-01-08 10:24:06

标签: c# sockets dispose finalizer xunit

我正在使用一些代码(不是我的,我不得不添加,我完全不相信这一点)对于一个打开套接字,发出请求和侦听响应的类,这会引发异常在xunit中测试时我无法理解的方式。我假设相同的异常发生在“实时”但是该类由单例引用,因此它可能只是隐藏。

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

此外,如果我只是捕获并吸收ObjectDisposedException,则xunit在命中该行以关闭侦听器线程时终止。

我只是不知道Socket在被要求关闭之前是如何处置的。

我对套接字的了解只是我在发现这个问题后所学到的,所以我不知道我是否提供了所需的一切。 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;
            }
        }
    }
}

3 个答案:

答案 0 :(得分:8)

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

在终结器中做这么多工作通常是一个坏主意(IMO)。我记不起上次实现终结器了 - 如果你实现了IDisposable,你应该没问题,除非你有直接引用非托管资源,这几乎总是以IntPtrs的形式。有序关闭应该是常态 - 如果程序正在关闭,或者有人忘记处理实例,那么终结器通常只能运行。

(我知道你在开始时澄清说这不是你的代码 - 我只是想我会解释为什么会有问题。如果你已经知道了部分/全部内容,请道歉。)

答案 1 :(得分:4)

由于垃圾收集器和终结器的工作方式,只有当您的类是非托管资源的直接所有者(例如Window Handle,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绝不能使用除释放的非托管资源和self之外的任何其他资源。如果不这样做,将有可能引用收集或处置的对象,因为在最终确定之前会暂时对对象进行重新保护。

答案 2 :(得分:0)

新信息:看起来我实际上遇到了两个问题,但是线程appears非常有毒。

从上面的MSDN链接:

  

“ThreadAbortException是一个特殊的   可以捕获的异常,但它   将自动再次提升   捕获块结束。“

一些非常有趣的社区内容也包括"Thread.Abort is a Sign of a Poorly Designed Program"

所以至少我现在有一些弹药来改变这种情况:)