2012-01-03 56 views
2

望着IDisposable模式+終結模式,有件事我不明白:IDisposable接口+終結模式

public class ComplexCleanupBase : IDisposable 
{ 
    private bool disposed = false; // to detect redundant calls 

    public ComplexCleanupBase() 
    { 
     // allocate resources 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if (!disposed) 
     { 
      if (disposing) 
      { 
       // dispose-only, i.e. non-finalizable logic 
      } 

      // shared cleanup logic 
      disposed = true; 
     } 
    } 

    ~ComplexCleanupBase() 
    { 
     Dispose(false); 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 
} 

從我的理解的模式應該像上面實現。

1)調用Dispose()觸發GC.SuppressFinalize(this),這意味着該對象不應該放在終結器隊列上,因爲它已經正確處理了?這有助於更快地釋放對象?

2)但是如果我根本就不在這個對象上調用Dispose()怎麼辦?在這種情況下,終結者應該踢進,對嗎?但是Dispose(false);完全沒有(只設置處置=真)。這是打算?感覺好像有什麼東西丟失......

回答

0

問題1:是的,如果GC.SuppressFinalize沒有被調用,對象將被放置在終結器隊列中並且將向上移動一代(如果不是已經是第2代的),這意味着該對象的內存直到新一代GC的下一次傳遞纔會被回收。

問題2:您的評論//shared cleanup logic是共享清理邏輯將去的地方,這是設置disposed = true以外的事情會發生的地方。

另外,順便說一句:如果你的處置邏輯應該只被調用一次,考慮收購一個lock,無爭議的鎖是非常快的。Net:

public class ComplexCleanupBase : IDisposable 
{ 
    private volatile bool disposed = false; // to detect redundant calls 
    private readonly object _mutex = new object(); 

    protected virtual void Dispose(bool disposing) 
    { 
    if (!Monitor.TryEnter(_mutex)) 
    { 
     // We're already being disposed. 
     return; 
    } 

    try 
    { 
     if (!disposed) 
     { 
     if (disposing) 
     { 
      // dispose-only, i.e. non-finalizable logic 
     } 

     // shared cleanup logic 
     disposed = true; 
     } 
    } 
    finally 
    { 
     Monitor.Exit(_mutex); 
    } 
    } 
    ... other methods unchanged 
} 
+0

終結器不應該阻止獲取鎖,即使鎖預計不會持續很長時間。如果某些事情導致鎖定不當,可能會阻止任何其他終結器運行。 – supercat 2014-01-20 00:40:38

+0

@supercat在這種情況下,呼叫在終結者中不可能被阻止。 '_mutex'只能用於處理目的。一般來說,你是正確的,但在這種情況下,你的觀點(應該是)是沒有意義的。 – 2014-01-20 10:12:20

+0

@supercat只是想到了一種情況,使我以前的評論不正確:如果在另一個實例的終結過程中,它會將ComplexCleanupBase的實例恢復生命,以便它在應用程序中具有另一個根,那麼可能會在互斥體上爭用,儘管這應該*真的*不會完成。答案更新完全一樣。 – 2014-01-20 12:58:30

0

如果的Dispose(假)是不會做任何事情,這是一個非常好的跡象表明,你的類或從它派生的任何類都不應該包含C#風格的「析構函數」,也不會覆蓋Finalize,而「disposing」參數應該被視爲一個虛擬元素,其目的是爲受保護的Dispose方法與公共簽名不同。

請注意,當父類不期望這種行爲時,在派生類中實現析構函數或重寫Finalize可以生成Heisenbugs。除此之外,GC有時可能會決定放棄一個類對象,甚至在正在使用該類的某個字段引用的實體時觸發它的終結器/析構函數。例如,假設一個靜態類usbThingie使用整數手柄操縱USB控制器和包裝類usbWrapper確實是這樣的:

 
    UInt32 myHandle; 
    void sendData(Byte data[]) 
    { 
    UsbThingie.send(myHandle, data[0], data.Length); 
    } 

如果送出數據()調用做的usbWrapper實例的最後一件事之前,它是被廢棄的時候,垃圾收集器可能會觀察到一旦UsbThingie.send()被調用 - 甚至在它返回之前 - usbWrapper將不再存在引用,因此它可以安全地觸發終結器。如果終結器嘗試關閉myHandle所提及的通道,則可能會中斷正在發生的傳輸;如果usbThingie不是線程安全的,那麼就不知道會發生什麼。