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

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

问题描述

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

对我来说,非托管"意味着诸如数据库连接、套接字、窗口句柄等.但是,我见过实现 Dispose() 方法以释放托管的代码 资源,这对我来说似乎是多余的,因为垃圾收集器应该为您处理这些.

例如:

公共类 MyCollection : IDisposable{私人列表<字符串>_theList = new List();私人字典<字符串,点>_theDict = new Dictionary();//死了,清除它!(免费的非托管资源)公共无效处置(){_theList.clear();_theDict.clear();_theList = null;_theDict = 空;}

我的问题是,这是否会使 MyCollection 使用的垃圾收集器空闲内存比平时更快?

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

解决方案

Dispose 的重点是释放非托管资源.它需要在某个时候完成,否则它们将永远不会被清理干净.垃圾收集器不知道如何IntPtr 类型的变量调用 DeleteHandle(),它不知道是否 是否需要调用DeleteHandle().

<块引用>

注意:什么是非托管资源?如果您在 Microsoft .NET Framework 中找到它:它是托管的.如果你自己去探索 MSDN,它是不受管理的.您使用 P/Invoke 调用摆脱 .NET Framework 中所有可用内容的舒适世界的任何内容都是非托管的 - 现在您有责任清理它.

您创建的对象需要公开一些方法,外部世界可以调用该方法,以便清理非托管资源.该方法可以随意命名:

public void Cleanup()

public void Shutdown()

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

public void Dispose()

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

公共接口IDisposable{无效处置()}

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

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

你已经完成了.除非你可以做得更好.


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

如果用户调用了Dispose()(意味着他们不再打算使用该对象)为什么不去掉那些浪费的位图和数据库连接?

那么现在我们将:

  • 摆脱非托管资源(因为我们必须这样做),并且
  • 摆脱托管资源(因为我们希望提供帮助)

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

public void Dispose(){//释放非托管资源Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);//也释放托管资源如果(this.databaseConnection != null){this.databaseConnection.Dispose();this.databaseConnection = null;}如果(this.frameBufferImage != null){this.frameBufferImage.Dispose();this.frameBufferImage = null;}}

一切都很好,除了你可以做得更好


如果该人忘记对您的对象调用Dispose()怎么办?然后他们会泄露一些不受管理的资源!

<块引用>

注意:它们不会泄漏托管资源,因为最终垃圾收集器将在后台线程上运行,并释放与任何未使用的内存相关的内存对象.这将包括您的对象以及您使用的任何托管对象(例如 BitmapDbConnection).

如果这个人忘记调用Dispose(),我们可以仍然保存他们的培根!我们仍然有办法为他们调用它:当垃圾收集器最终开始释放(即完成)我们的对象时.

<块引用>

注意:垃圾收集器最终会释放所有托管对象.当它发生时,它调用 Finalize对象上的方法.GC 不知道,或者关心你的 Dispose 方法.那只是我们选择的名字当我们想要获取时调用的方法摆脱不受管理的东西.

垃圾收集器销毁我们的对象是释放那些讨厌的非托管资源的最佳时机.我们通过覆盖 Finalize() 方法来实现这一点.

<块引用>

注意:在 C# 中,您不会显式覆盖 Finalize() 方法.您编写了一个看起来像C++ 析构函数的方法,并且编译器将其作为 Finalize() 方法的实现:

~MyObject(){//我们正在完成(即销毁),如果用户忘记调用 Dispose处置();//<--警告:微妙的错误!继续阅读!}

但是该代码中有一个错误.你看,垃圾收集器在后台线程上运行;您不知道销毁两个对象的顺序.完全有可能在您的 Dispose() 代码中,您试图摆脱的 ma​​naged 对象(因为您想提供帮助)不再存在:

public void Dispose(){//释放非托管资源Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);//也释放托管资源如果(this.databaseConnection != null){this.databaseConnection.Dispose();//<-- 崩溃,GC 已经销毁了它this.databaseConnection = null;}如果(this.frameBufferImage != null){this.frameBufferImage.Dispose();//<-- 崩溃,GC 已经销毁了它this.frameBufferImage = null;}}

所以你需要的是一种让 Finalize() 告诉 Dispose() 它应该不接触任何托管资源的方法(因为它们可能不再存在),同时仍在释放非托管资源.

执行此操作的标准模式是让Finalize()Dispose() 都调用第三个(!) 方法;如果您从 Dispose()(而不是 Finalize())调用它,则传递一个布尔值,这意味着释放托管资源是安全的.

这个内部方法可以被赋予任意的名称,如CoreDispose"或MyInternalDispose",但传统上称它为Dispose(Boolean):

protected void Dispose(Boolean disposing)

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

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects){//释放非托管资源Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);//也释放托管资源,但前提是我被 Dispose 调用//(如果我是从 Finalize 调用的,那么对象可能不存在//没有了if (itIsSafeToAlsoFreeManagedObjects){如果(this.databaseConnection != null){this.databaseConnection.Dispose();this.databaseConnection = null;}如果(this.frameBufferImage != null){this.frameBufferImage.Dispose();this.frameBufferImage = null;}}}

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

public void Dispose(){处置(真);//我从Dispose打电话给你,很安全}

和您的终结器:

~MyObject(){处置(假);//我*不*从Dispose打电话给你,它*不*安全}

<块引用>

注意:如果您的对象从一个实现了Dispose的对象派生而来,那么不要忘记调用它们的基础 Dispose方法覆盖处置:

public override void Dispose(){尝试{处置(真);//true: 释放托管资源是安全的}最后{基地.处置();}}

一切都很好,除了你可以做得更好


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

这不仅是浪费,而且如果您的对象对您在 最后 调用 Dispose() 时已经处理的对象有垃圾引用,您将尝试再次处理它们!

您会注意到,在我的代码中,我小心地删除了对我已处理的对象的引用,因此我不会尝试对垃圾对象引用调用 Dispose.但这并没有阻止一个微妙的错误潜入.

当用户调用Dispose()时:句柄CursorFileBitmapIconServiceHandle被销毁.稍后当垃圾收集器运行时,它会再次尝试销毁同一个句柄.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize){//释放非托管资源Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);//<--双重销毁...}

解决这个问题的方法是告诉垃圾收集器它不需要费心完成对象——它的资源已经被清理干净,不需要更多的工作.您可以通过在 Dispose() 方法中调用 GC.SuppressFinalize() 来实现:

public void Dispose(){处置(真);//我从Dispose打电话给你,很安全GC.SuppressFinalize(this);//嘿,GC:不要打扰稍后调用finalize}

既然用户已经调用了Dispose(),我们有:

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

运行终结器的 GC 毫无意义——一切都已处理完毕.

我不能使用 Finalize 来清理非托管资源吗?

Object.Finalize 说:

<块引用>

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

但 MSDN 文档也说,对于 IDisposable.Dispose:

<块引用>

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

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

<块引用>

这是你的选择!但是选择Dispose.

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

~MyObject(){//释放非托管资源Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);//C# 析构函数会自动调用其基类的析构函数.}

问题是你不知道垃圾收集器什么时候会结束你的对象.在垃圾收集器最终运行之前,您未管理的、不需要的、未使用的本机资源将一直存在.然后它会调用你的终结器方法;清理非托管资源.Object.Finalize 的文档指出了这一点:

<块引用>

终结器执行的确切时间未定义.为确保类实例资源的确定性释放,请实施 Close 方法或提供 IDisposable.Dispose 实现.

这是使用Dispose清理非托管资源的优点;您可以了解和控制何时清理非托管资源.它们的破坏是确定性的".


回答你最初的问题:为什么不现在释放内存,而不是在 GC 决定时释放内存?我有一个面部识别软件,需要删除 530 MB 的内部图像现在,因为它们不再需要.如果我们不这样做:机器会停止交换.

奖励阅读

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

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

我认为每个曾经处理过对象或 COM 的程序员至少应该阅读第一章.这是有史以来最好的解释.

额外奖励阅读

当你知道的一切都是错误 存档埃里克·利珀特

<块引用>

因此确实很难编写正确的终结器,我能给你的最好建议是不要尝试.

I know from reading the Microsoft 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 Framework 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()

or

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 override void 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 unmanaged 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.

Extra Bonus Reading

When everything you know is wrong archiveby Eric Lippert

It is therefore very difficult indeed to write a correct finalizer, and the best advice I can give you is to not try.

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

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