2009-01-08 52 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"

所以至少我有一些彈藥讓現在改變:)