正确使用IDisposable接口的 [英] Proper use of the IDisposable interface

查看:503
本文介绍了正确使用IDisposable接口的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道从阅读 MSDN文档的是主的使用IDisposable接口是清理非托管资源。

在我看来,非托管是指像数据库连接器,插座,窗口句柄,等等。但是,我已经看到了code其中Dispose方法实现自由的管理的资源,这似乎是多余的我,因为垃圾收集器应该照顾你们。

例如:

 公共类MyCollection中:IDisposable的
{
    私人列表<字符串> _theList =新的名单,其中,字符串>();
    私人字典<串,点> _theDict =新字典<串,点>();

    //模具,清除它了! (免费非托管资源)
    公共无效的Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = NULL;
        _theDict = NULL;
    }
 

我的问题是,这是否通过MyCollection的任何使用速度更快的垃圾收集器释放内存比它通常会?

修改:到目前为止,人们已经发布使用IDisposable接口清理非托管资源,如数据库连接和位图的一些很好的例子。但是假设_theList在上面code含有万元字符串,你想释放内存的现在的,而不是等待垃圾回收器。将上述code实现这一目标?

解决方案

处置点的以释放非托管资源。它需要在某些时候完成的,否则他们将永远不会被清除。垃圾收集器不知道如何调用 DeleteHandle()对类型的变量的IntPtr ,它不知道的是否或不是需要调用 DeleteHandle()

  

注意:什么是非托管资源的?如果你发现它在微软.NET框架:它的管理。如果你去MSDN周围戳自己,它的非托管。任何你曾经使用过的P / Invoke调用的.NET Framwork提供给你的一切的漂亮舒适的世界,以获得外界的是非托管的 - 而你现在负责清理起来

您已经创建了需要公开的对象的部分的方法,外界可以调用,以清理非托管资源。该方法可以被命名为任何你喜欢的:

 公共无效清除()

公共无效关闭()
 

但取而代之的是这个方法的规范的名称:

 公共无效的Dispose()
 

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

 公共接口IDisposable的
{
   无效的Dispose()
}
 

所以,你做你的对象暴露的IDisposable 界面,你承诺的方式,你已经写了一个方法来清理非托管资源:

 公共无效的Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
 

和你就大功告成了。 除了可以做的更好。


如果你的对象已经分配了250MB的 <一个href="http://msdn.microsoft.com/en-us/library/system.drawing.bitmap.aspx">System.Drawing.Bitmap (即.NET托管Bitmap类)作为某种帧缓冲?当然,这是一个托管的.NET对象,垃圾收集器将释放它。但你真的想离开的内存只是坐在那里250MB - 等待垃圾回收器的最终的到来和自由呢?如果有一个开放式数据库连接的?当然,我们并不想就此坐开,等待GC敲定的对象。

如果用户名为的Dispose()(这意味着他们不再打算使用的对象),为什么不能摆脱那些无用的位图和数据库连接的?

所以,现在,我们将:

  • 摆脱非托管资源(因为我们要)和
  • 在摆脱管理的资源(因为我们希望对大家有所帮助)

让我们更新我们的的Dispose()方法来摆脱这些管理对象的:

 公共无效的Dispose()
{
   //自由非托管资源
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //自由管理的资源太
   如果(this.databaseConnection!= NULL)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = NULL;
   }
   如果(this.frameBufferImage!= NULL)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = NULL;
   }
}
 

和一切都很好,除了可以做的更好


如果有什么人的忘记调用的Dispose()你的对象?然后,他们会泄露一些非托管的资源!

  

注意:他们不会泄漏管理的资源,因为最终的垃圾收集器会在后台线程运行,并提供免费的任何未使用的相关联的内存对象。这将包括你的对象,和任何管理对象使用(如位图的DbConnection

如果这个人忘了叫的Dispose(),我们就可以的还是的拯救他们的腊肉!我们还有一个方法来调用它的的他们:当垃圾回收器终于得到周围释放(即最后确定)我们的对象

  

注意:垃圾收集器将最终释放所有被管理对象。   如果是这样,它调用的 的Finalize   方法的对象上。在GC不知道,或   护理,约的处置方式。   这只是我们选择了一个名字   我们打​​电话的时候,我们希望得到一个方法   摆脱非托管的东西。

由垃圾收集我们的对象的破坏是的完美的释放那些讨厌的非托管资源的时间。为此,我们通过覆盖的Finalize()方法。

注意:在C#中,您没有明确覆盖的Finalize()方法。你写一个方法,它的看起来的一个 C ++的析构函数,然后编译器会认为是你执行的Finalize()方法:

 〜MyObject来()
{
    //我们正在定稿(即破坏),调用Dispose的情况下,用户忘记
    的Dispose(); //&LT;  - 警告:微妙的错误!继续阅读!
}
 

但是,有在code的错误。你看,垃圾回收器上的后台线程运行;你不知道其中两个对象被销毁的顺序。这是完全可能的,在你的的Dispose() code,在管理你想摆脱(因为你想要的对象是有益的),就不再出现了:

 公共无效的Dispose()
{
   //自由非托管资源
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //自由管理的资源太
   如果(this.databaseConnection!= NULL)
   {
      this.databaseConnection.Dispose(); //&LT;  - 崩溃,GC已经摧毁了它
      this.databaseConnection = NULL;
   }
   如果(this.frameBufferImage!= NULL)
   {
      this.frameBufferImage.Dispose(); //&LT;  - 崩溃,GC已经摧毁了它
      this.frameBufferImage = NULL;
   }
}
 

所以,你需要的是一种方法,的Finalize()来告诉的Dispose(),它应<强>不要触摸任何管理资源(因为它们的可能不会有的了),同时还释放非托管资源。

标准的模式来做到这一点是有的Finalize()的Dispose()都调用一个<强>第三办法(!);在那里你传递一个布尔说,如果你从调用它的Dispose()(而不是的Finalize()) ,这意味着它是安全的释放托管资源。

本的的内部的方法的可以的给予一定的任意名称,如CoreDispose,或MyInternalDispose,但传统称之为处置(布尔)

 保护无效的Dispose(布尔处置)
 

但是,一个更加有用的参数名称可能是:

 保护无效的Dispose(布尔itIsSafeToAlsoFreeManagedObjects)
{
   //自由非托管资源
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //自由管理的资源太多,但只有当我正在从处置称为
   //(如果我正从所谓的Finalize那么对象可能不存在
   //了
   如果(itIsSafeToAlsoFreeManagedObjects)
   {
      如果(this.databaseConnection!= NULL)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = NULL;
      }
      如果(this.frameBufferImage!= NULL)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = NULL;
      }
   }
}
 

和你改变你的落实 IDisposable.Dispose()方法为:

 公共无效的Dispose()
{
   处置(真); //我打电话是从配置,它是安全的
}
 

和你的终结为:

 〜MyObject来()
{
   处置(假); //我*不*喊你从配置,这是*不*安全
}
 

  

注意:如果你的对象从一个对象来实现处置,然后不要忘记调用他们的基础 Dispose方法,当你重写配置:

 公共的Dispose()
{
    尝试
    {
        处置(真); //真:安全释放托管资源
    }
    最后
    {
        base.Dispose();
    }
}
 

和一切都很好,除了可以做的更好


如果用户来电的Dispose()你的对象,那么一切都已经被清理。后来,当垃圾收集而来并调用的Finalize,它就会调用处置了。

这不仅是一种浪费,但如果你的对象有垃圾引用你已经从设置的对象的最后一个致电的Dispose() ,你会尝试再次处理掉!

您会发现在我的code我用心删除对我处置的对象,所以我不尝试调用处置上垃圾对象引用。但是,这并没有侵入,阻止一个微妙的问题。

在用户来电的Dispose():手柄 CursorFileBitmapIconServiceHandle 被破坏。后来垃圾收集器运行时,它会尝试再次摧毁相同的句柄。

 保护无效的Dispose(布尔iAmBeingCalledFromDisposeAndNotFinalize)
{
   //自由非托管资源
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //&LT;  - 双灭
   ...
}
 

您解决这个问题的办法是告诉垃圾收集器,它不需要理会敲定对象 - 其资源已经被清理,并没有更多的工作要做。您可以通过调用 GC.Sup pressFinalize()的Dispose()方法:

 公共无效的Dispose()
{
   处置(真); //我打电话是从配置,它是安全的
   GC.Sup pressFinalize(本); //嘿嘿,GC:不要打扰呼叫后敲定
}
 

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

  • 在释放非托管资源
  • 在释放托管资源

有一个在运行终结的GC没有意义 - 一切都照顾

无法我用最后确定清理非托管资源?

的文档<一个href="https://msdn.microsoft.com/en-us/library/system.object.finalize.aspx"><$c$c>Object.Finalize说:

  

finalize方法被用于由之前的对象被销毁当前对象持有的非托管资源执行清理操作。

但MSDN文档还表示,对于<一href="https://msdn.microsoft.com/en-us/library/system.idisposable.dispose(v=vs.110).aspx"><$c$c>IDisposable.Dispose:

  

执行与释放或重置非托管资源相关的应用程序定义的任务。

因此​​,这是什么呢?哪一个是适合我的地方清理非托管资源?答案是:

  

这是你的选择!但是,选择处置

您当然可以把你的unamanged清理的终结:

 〜MyObject来()
{
   //自由非托管资源
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   // C#析构函数自动调用基类的析构函数。
}
 

与该问题是你不知道什么时候垃圾收集器将得到解决,以最终确定您的对象。你的联合国管理,联合国需要,未使用的原生资源将坚持围绕直到垃圾收集器的最终的运行。然后,它会调用你的释放方法;清理非托管资源。的文档的 Object.Finalize 点了这一点:

  

终结执行时的准确时间是不确定的。为确保您的类的实例确定性释放资源,实行的关闭方式或提供<一href="https://msdn.microsoft.com/en-us/library/system.idisposable.dispose(v=vs.110).aspx"><$c$c>IDisposable.Dispose实施

这是由于使用了处置来清理非托管资源;你了解和控制,当非托管资源的清理。他们破坏的确定性的。


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

奖金读

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

在35页,他解释使用二进制对象的问题,在你的眼前发明了COM。一旦你明白为什么COM的的 的,剩余的300页是显而易见的,只是细节微软的实现。

我想谁曾经处理的对象或COM每个程序员应该,最起码,阅读第一章。这是什么有史以来最好的解释。

I know from reading the MSDN documentation that the "primary" use of the IDisposable interface is to clean up unmanaged resources.

To me, "unmanaged" means things like database connections, sockets, window handles, etc. But, I've seen code where the Dispose method is implemented to free managed resources, which seems redundant to me, since the garbage collector should take care of that for you.

For example:

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;
    }

My question is, does this make the garbage collector free memory used by MyCollection any faster than it normally would?

edit: So far people have posted some good examples of using IDisposable to clean up unmanaged resources such as database connections and bitmaps. But suppose that _theList in the above code contained a million strings, and you wanted to free that memory now, rather than waiting for the garbage collector. Would the above code accomplish that?

解决方案

The point of Dispose is to free unmanaged resources. It needs to be done at some point, otherwise they will never be cleaned up. The garbage collector doesn't know how to call DeleteHandle() on a variable of type IntPtr, it doesn't know whether or not it needs to call 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.

The object that you've created needs to expose some method, that the outside world can call, in order to clean up unmanaged resources. The method can be named whatever you like:

public void Cleanup()

public void Shutdown()

But instead there is a standardized name for this method:

public void Dispose()

There was even an interface created, IDisposable, that has just that one method:

public interface IDisposable
{
   void Dispose()
}

So you make your object expose the IDisposable interface, and that way you promise that you've written that single method to clean up your unmanaged resources:

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

And you're done. Except you can do better.


What if your object has allocated a 250MB System.Drawing.Bitmap (i.e. the .NET managed Bitmap class) as some sort of frame buffer? Sure, this is a managed .NET object, and the garbage collector will free it. But do you really want to leave 250MB of memory just sitting there – waiting for the garbage collector to eventually come along and free it? What if there's an open database connection? Surely we don't want that connection sitting open, waiting for the GC to finalize the object.

If the user has called Dispose() (meaning they no longer plan to use the object) why not get rid of those wasteful bitmaps and database connections?

So now we will:

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

So let's update our Dispose() method to get rid of those managed objects:

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;
   }
}

And all is good, except you can do better!


What if the person forgot to call Dispose() on your object? Then they would leak some unmanaged resources!

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).

If the person forgot to call Dispose(), we can still save their bacon! We still have a way to call it for them: when the garbage collector finally gets around to freeing (i.e. finalizing) our object.

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.

The destruction of our object by the Garbage collector is the perfect time to free those pesky unmanaged resources. We do this by overriding the Finalize() method.

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!
}

But there's a bug in that code. You see, the garbage collector runs on a background thread; you don't know the order in which two objects are destroyed. It is entirely possible that in your Dispose() code, the managed object you're trying to get rid of (because you wanted to be helpful) is no longer there:

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;
   }
}

So what you need is a way for Finalize() to tell Dispose() that it should not touch any managed resources (because they might not be there anymore), while still freeing unmanaged resources.

The standard pattern to do this is to have Finalize() and Dispose() both call a third(!) method; where you pass a Boolean saying if you're calling it from Dispose() (as opposed to Finalize()), meaning it's safe to free managed resources.

This internal method could be given some arbitrary name like "CoreDispose", or "MyInternalDispose", but is tradition to call it Dispose(Boolean):

protected void Dispose(Boolean disposing)

But a more helpful parameter name might be:

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;
      }
   }
}

And you change your implementation of the IDisposable.Dispose() method to:

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

and your finalizer to:

~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();
    }
}

And all is good, except you can do better!


If the user calls Dispose() on your object, then everything has been cleaned up. Later on, when the garbage collector comes along and calls Finalize, it will then call Dispose again.

Not only is this wasteful, but if your object has junk references to objects you already disposed of from the last call to Dispose(), you'll try to dispose them again!

You'll notice in my code I was careful to remove references to objects that I've disposed, so I don't try to call Dispose on a junk object reference. But that didn't stop a subtle bug from creeping in.

When the user calls Dispose(): the handle CursorFileBitmapIconServiceHandle is destroyed. Later when the garbage collector runs, it will try to destroy the same handle again.

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

The way you fix this is tell the garbage collector that it doesn't need to bother finalizing the object – its resources have already been cleaned up, and no more work is needed. You do this by calling GC.SuppressFinalize() in the Dispose() method:

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
}

Now that the user has called Dispose(), we have:

  • freed unmanaged resources
  • freed managed resources

There's no point in the GC running the finalizer – everything's taken care of.

Couldn't I use Finalize to cleanup unmanaged resources?

The documentation for Object.Finalize says:

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

But the MSDN documentation also says, for IDisposable.Dispose:

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

So which is it? Which one is the place for me to cleanup unmanaged resources? The answer is:

It's your choice! But choose Dispose.

You certainly could place your unamanged cleanup in the finalizer:

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

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

The problem with that is you have no idea when the garbage collector will get around to finalizing your object. Your un-managed, un-needed, un-used native resources will stick around until the garbage collector eventually runs. Then it will call your finalizer method; cleaning up unmanaged resources. The documentation of Object.Finalize points this out:

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.

This is the virtue of using Dispose to cleanup unmanaged resources; you get to know, and control, when unmanaged resource are cleaned up. Their destruction is "deterministic".


To answer your original question: Why not release memory now, rather than for when the GC decides to do it? I have a facial recognition software that needs to get rid of 530 MB of internal images now, since they're no longer needed. When we don't: the machine grinds to a swapping halt.

Bonus Reading

For anyone who likes the style of this answer (explaining the why, so the how becomes obvious), I suggest you read Chapter One of Don Box's Essential COM:

In 35 pages he explains the problems of using binary objects, and invents COM before your eyes. Once you realize the why of COM, the remaining 300 pages are obvious, and just detail Microsoft's implementation.

I think every programmer who has ever dealt with objects or COM should, at the very least, read the first chapter. It is the best explanation of anything ever.

这篇关于正确使用IDisposable接口的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆