问题

通过阅读 MSDN文档,我知道"主要"使用IDisposable接口是清理非托管资源.

对我来说,"非托管"意味着像数据库连接,套接字,窗口句柄等.但是,我看到的代码中,Dispose方法被实现以释放托管的资源,这似乎是多余的对我来说,因为垃圾收集器应该照顾你.

例如:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

我的问题是,这是否使MyCollection使用的垃圾收集器自由内存比任何更快?

编辑:到目前为止,人们已经发布了一些使用IDisposable来清理非托管资源(如数据库连接和位图)的好例子.但是假设_theList在上面的代码包含了一百万个字符串,你想释放该内存现在,而不是等待垃圾收集器.上述代码是否可以实现这一点?



解决方法

处置的点可释放非托管资源.它需要在某个时间点完成,否则它们永远不会被清除.垃圾收集器不知道如何 IntPtr 类型的变量上调用 DeleteHandle(),它不知道是否,否则需要调用 DeleteHandle().

Note: What is an unmanaged resource? If you found it in the Microsoft .NET Framework: it's managed. If you went poking around MSDN yourself, it's unmanaged. Anything you've used P/Invoke calls to get outside of the nice comfy world of everything available to you in the .NET Framwork is unmanaged – and you're now responsible for cleaning it up.

您创建的对象需要公开外部世界可以调用的一些方法,以清理非托管资源.该方法可以命名为任何你喜欢的:

public void Cleanup()

public void Shutdown()

但是这个方法有一个标准化的名称:

public void Dispose()

甚至还创建了一个接口, IDisposable ,只有一个方法:

public interface IDisposable
{
   void Dispose()
}

所以你让你的对象公开 IDisposable 接口,这样你就承诺你写了这个单一的方法来清理你的非托管资源:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

你做完了. 除非你能做得更好.


如果您的对象已分配了250MB 系统.Drawing.Bitmap (即.NET管理的Bitmap类)作为某种类型的帧缓冲区?当然,这是一个托管的.NET对象,垃圾回收器会释放它.但你真的想留下250MB的内存只是坐在那里 - 等待垃圾收集器最终来和释放它?如果存在打开数据库连接,该怎么办?当然,我们不希望该连接处于打开状态,等待GC完成对象.

如果用户已经调用 Dispose()(意味着他们不再计划使用对象)为什么不摆脱那些浪费的位图和数据库连接?

现在我们将:

  • get rid of unmanaged resources (because we have to), and
  • get rid of managed resources (because we want to be helpful)

因此,让我们更新我们的 Dispose()方法以摆脱那些托管对象:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

一切都很好,除非你能做得更好!


如果使用者忘记在物件上呼叫 Dispose(),该怎么办?然后,他们会泄漏一些非托管资源!

Note: They won't leak managed resources, because eventually the garbage collector is going to run, on a background thread, and free the memory associated with any unused objects. This will include your object, and any managed objects you use (e.g. the Bitmap and the DbConnection).

如果使用者忘记致电 Dispose(),我们可以 保存他们的培根!我们还有一种方法来为它们调用::当垃圾收集器最终到达释放(即完成)我们的对象.

Note: The garbage collector will eventually free all managed objects. When it does, it calls the Finalize method on the object. The GC doesn't know, or care, about your Dispose method. That was just a name we chose for a method we call when we want to get rid of unmanaged stuff.

垃圾收集器对我们的对象的破坏是完美时间来释放那些讨厌的非托管资源.我们通过覆盖 Finalize()方法来实现.

Note: In C#, you don't explicitly override the Finalize() method. You write a method that looks like a C++ destructor, and the compiler takes that to be your implementation of the Finalize() method:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

但是代码中有一个错误.你看,垃圾收集器在后台线程上运行;你不知道两个对象被销毁的顺序.完全有可能在您的 Dispose()代码中,您想要移除的托管对象(因为您希望获得帮助)不再存在:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

所以你需要的是一个方法, Finalize()告诉 Dispose()它应该不触摸任何托管资源他们可能不在那里),同时仍释放非托管资源.

这样做的标准模式是 Finalize() Dispose()都调用第三个如果你从 Dispose()(而不是 Finalize())调用它,那么你传递一个布尔值,意味着可以安全地释放托管资源. >

可以给出一些任意名称,例如"CoreDispose"或"MyInternalDispose" / code>:

protected void Dispose(Boolean disposing)

但更有用的参数名称可能是:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

您将 IDisposable.Dispose()方法的实现更改为:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

您的终结者:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Note: If your object descends from an object that implements Dispose, then don't forget to call their base Dispose method when you override Dispose:

public Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

一切都很好,除非你能做得更好!


如果用户在对象上调用 Dispose(),那么一切都已清除.稍后,当垃圾收集器出现并调用Finalize时,它将再次调用 Dispose .

这不仅浪费,而且如果你的对象有垃圾引用你从最后调用 Dispose()已经处置的对象,你会尝试再处理一次!

您会注意到在我的代码中,我很小心地删除对我已经处理的对象的引用,所以我不尝试调用 Dispose 在垃圾对象引用.但这并没有阻止一个细微的bug蔓延.

当用户调用 Dispose()时,会破坏句柄 CursorFileBitmapIconServiceHandle .后来当垃圾收集器运行时,它将再次尝试销毁相同的句柄.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

你解决这个问题的方式告诉垃圾收集器它不需要麻烦完成对象 - 它的资源已经被清理,并且不需要更多的工作.你可以通过调用 Dispose()方法中的 GC.SuppressFinalize()来实现:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

现在用户已经调用 Dispose(),我们有:

  • freed unmanaged resources
  • freed managed resources

GC运行终结器没有任何意义 - 一切都在处理.

Couldn't I use Finalize to cleanup unmanaged resources?

Object.Finalize 说:

The Finalize method is used to perform cleanup operations on unmanaged resources held by the current object before the object is destroyed.

但是MSDN文档也说, IDisposable.Dispose :

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

那是什么呢?哪一个是我清理非托管资源的地方?答案是:

It's your choice! But choose Dispose.

您当然可以将您的非托管清理放置在终结器中:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

这样的问题是,你不知道垃圾收集器何时会完成你的对象.您的未管理的,不需要的,未使用的本机资源将坚持到垃圾收集器最终运行.然后它会调用你的终结方法;清理非托管资源. Object.Finalize 的文档指出了这一点:

The exact time when the finalizer executes is undefined. To ensure deterministic release of resources for instances of your class, implement a Close method or provide a IDisposable.Dispose implementation.

这是使用 Dispose 清除非托管资源的优点;你知道,并控制,当非托管资源清理.它们的破坏是"确定性的".


要回答你原来的问题:为什么不释放内存,而不是当GC决定这样做?我有一个面部识别软件,需要以摆脱530MB的内部图像现在,因为他们不再需要.当我们没有:机器研磨到交换暂停.

Bonus Reading

对于喜欢这个答案风格的任何人(解释为什么,所以如何变得明显),我建议你阅读第一章Don Box的基本COM:

在35页中,他解释了使用二进制对象的问题,并在你的眼前发明COM.一旦你意识到为什么 COM,剩下的300页是显而易见的,只是详细的Microsoft的实现.

我认为每个处理对象或COM的程序员至少应该阅读第一章.这是对任何东西的最好的解释.




相关问题推荐