2009-01-08 51 views
2

我正在使用一些代码(不是我的,我急于添加,我不太相信这些),它会打开一个套接字,发出请求并侦听响应,这会引发异常当我在xunit中测试时我无法理解。我假设发生同样的异常“活”,但类是由单身人士引用,因此它可能只是隐藏。处理socket/finalizing两次的问题?

该问题在xunit中显示为“System.CannotUnloadAppDomainException:卸载appdomain时出错”,内部异常是在关闭套接字时抛出(基本上)在终结器中的“System.ObjectDisposedException”!没有其他引用关于调用close和socket的套接字在Socket类中受到保护,所以我不清楚该对象可以如何处置。此外,如果我只是捕获并吸收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; 
      } 
     } 
    } 
} 

回答

8

如果该对象符合垃圾收集和有插座上的任何其它引用,那么插座的终结很可能你的对象的终结之前运行。我怀疑这是发生在这里的事情。

在终结器中完成这项工作通常是一个糟糕的主意(IMO)。我不记得上一次我实现了一个终结器 - 如果你实现了IDisposable,你应该没问题,除非你有直接引用非托管资源,它几乎总是以IntPtrs的形式。有序关闭应该是常态 - 只有在程序关闭或者某人忘记处理实例开始时,通常才会运行终结器。

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

+0

感谢您的。鉴于该类是在一个预计会持续项目生命周期的单例中引用的,finaliser *在关闭时(通过IIS或xunit的拆卸)正在运行 - 如果GC将拾取套接字和线程,则此类需要根本无法解决问题? – annakata 2009-01-08 10:55:22

+0

(应该说应用程序实例的生命周期,而不是项目) – annakata 2009-01-08 10:56:01

4

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

终结器不得尝试处置或甚至使用受管资源,否则您将面临调用已终止或处置对象的风险。

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

一言以蔽之:

  • 如果你的对象是持有非托管资源,你应该实现IDisposable,你必须执行终结。
  • 如果你的对象持有一个IDiposable对象,它也应该自己实现IDisposable并明确地处理该对象。
  • 如果您的对象同时拥有非托管和一次性,终结器必须调用两个不同版本的Dispose,一个释放一次性和非托管,另一个只能非托管。这通常使用由Dipose()和Finalizer()调用的Dispose(bool)函数完成。
  • 终结者不得使用除释放的非托管资源和自身以外的任何其他资源。如果不这样做,将会引用引用收集或处理的对象的风险,因为对象在定稿之前会暂时进行修改。
0

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

从上面的MSDN链接:

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

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

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