2012-01-03 36 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不是线程安全的,那么就不知道会发生什么。