IDisposable +终结者模式

时间:2012-01-03 17:22:24

标签: garbage-collection dispose idisposable finalizer

查看IDisposable模式+ Finalizer模式,有些事我不明白:

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()怎么办?在那种情况下,终结者应该开始,对吗?但处理(假);什么都不做(只设置dispos = true)。这是有意的吗?感觉好像缺少某些东西......

2 个答案:

答案 0 :(得分: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
}

答案 1 :(得分:0)

如果Dispose(false)不会做任何事情,那么这是一个非常好的迹象,表明你的类或从它派生的任何类都不应该包含C#-style“析构函数”,也不能覆盖Finalize和“disposing”参数应被视为一个虚拟人,其目的是为受保护的Dispose方法提供与公共方法不同的签名。

请注意,当父类不期望这样的行为时,在派生类中实现析构函数或重写Finalize可以生成Heisenbugs。除其他事项外,GC有时可以决定放弃一个类对象,触发其终结器/析构函数,即使正在使用该类字段引用的实体时也是如此。例如,假设静态类usbThingie使用整数句柄操作USB控制器,而包装类usbWrapper执行如下操作:

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

如果sendData()调用是usbWrapper实例在放弃之前做的最后一件事,那么垃圾收集器可能会在调用UsbThingie.send()后观察它 - 甚至在它返回之前 - usbWrapper不存在进一步的引用,因此它可以安全地触发终结器。如果终结器试图关闭myHandle引用的通道,则可能会破坏正在发生的传输;如果usbThingie不是线程安全的,那么就不知道会发生什么。