c# 可恶的c# - 正确使用IDisposable接口




dispose用法 (16)

通过阅读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使用的垃圾回收器可用内存比MyCollection更快?

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


Answers

First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn't know how to deal with such objects. If class has for example only value types, then I don't consider this class as class with unmanaged resources. For my code I follow next practices:

  1. If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
  2. Clean objects as soon as I finished usage of it.
  3. In my dispose method I iterate over all IDisposable members of class and call Dispose.
  4. In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
  5. As additional precaution I try to make possible calling of Dispose() multiple times.
  6. Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
    Following template demonstrates what I described in words as sample of code:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

If anything, I'd expect the code to be less efficient than when leaving it out.

Calling the Clear() methods are unnecessary, and the GC probably wouldn't do that if the Dispose didn't do it...


There are things that the Dispose() operation does in the example code that might have an effect that would not occur due to a normal GC of the MyCollection object.

If the objects referenced by _theList or _theDict are referred to by other objects, then that List<> or Dictionary<> object will not be subject to collection but will suddenly have no contents. If there were no Dispose() operation as in the example, those collections would still contain their contents.

Of course, if this were the situation I would call it a broken design - I'm just pointing out (pedantically, I suppose) that the Dispose() operation might not be completely redundant, depending on whether there are other uses of the List<> or Dictionary<> that are not shown in the fragment.


In the example you posted, it still doesn't "free the memory now". All memory is garbage collected, but it may allow the memory to be collected in an earlier generation . You'd have to run some tests to be sure.

The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.

I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commit call never happened it would then call Rollback on itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

I've also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

So here are a couple of concrete examples that don't do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.


The most justifiable use case for disposal of managed resources, is preparation for the GC to reclaim resources that would otherwise never be collected.

A prime example is circular references.

Whilst it's best practice to use patterns that avoid circular references, if you do end up with (for example) a 'child' object that has a reference back to its 'parent', this can stop GC collection of the parent if you just abandon the reference and rely on GC - plus if you have implemented a finalizer, it'll never be called.

The only way round this is to manually break the circular references by setting the Parent references to null on the children.

Implementing IDisposable on parent and children is the best way to do this. When Dispose is called on the Parent, call Dispose on all Children, and in the child Dispose method, set the Parent references to null.


您提供的代码示例不是一个很好的IDisposable使用示例。字典清理通常不应该去该Dispose方法。字典项目在超出范围时将被清除并处理。IDisposable需要实现来释放一些内存/处理程序,即使在超出范围之后也不会释放/释放。

下面的例子展示了一个带有一些代码和注释的IDisposable模式的好例子。

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

Answer to your edit: Sort of. If I do this:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

It's functionally identical to this for purposes of memory management:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

If you really really really need to free the memory this very instant, call GC.Collect() . There's no reason to do this here, though. The memory will be freed when it's needed.


I won't repeat the usual stuff about Using or freeing un-managed resources, that has all been covered. But I would like to point out what seems a common misconception.
Given the following code

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

I realise that the Disposable implementation does not follow current guidelines, but hopefully you all get the idea.
Now, when Dispose is called, how much memory gets freed?

Answer: None.
Calling Dispose can release unmanaged resources, it CANNOT reclaim managed memory, only the GC can do that. Thats not to say that the above isn't a good idea, following the above pattern is still a good idea in fact. Once Dispose has been run, there is nothing stopping the GC re-claiming the memory that was being used by _Large, even though the instance of LargeStuff may still be in scope. The strings in _Large may also be in gen 0 but the instance of LargeStuff might be gen 2, so again, memory would be re-claimed sooner.
There is no point in adding a finaliser to call the Dispose method shown above though. That will just DELAY the re-claiming of memory to allow the finaliser to run.


IDisposable is good for unsubscribing from events.


One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.



Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I've written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties ...


如果MyCollection将被垃圾收集,那么你不需要处理它。 这样做只会使CPU运行得更多,甚至会导致垃圾收集器已经执行的一些预先计算的分析无效。

我使用IDisposable来执行诸如确保线程正确放置以及非托管资源的操作。

编辑为了回应Scott的评论:

GC性能度量标准受到影响的唯一时机是当[GC] GC.Collect()调用被调用时“

从概念上讲,GC维护对象参考图的视图,以及从线程的堆栈帧引用它的所有引用。 这个堆可能非常大并且跨越多页内存。 作为一种优化,GC会缓存对不太经常更改的页面的分析,以避免不必要地重新扫描页面。 GC在页面中的数据发生更改时收到来自内核的通知,因此它知道该页面很脏并且需要重新扫描。 如果集合在Gen0中,那么页面中的其他内容可能也会发生变化,但在Gen1和Gen2中这种情况不太可能发生。 有趣的是,这些钩子在Mac OS X中不适用于将GC移植到Mac的团队,以便让Silverlight插件在该平台上工作。

另一点反对不必要的资源处置:想象一个过程卸载的情况。 想象一下,这个过程已经运行了一段时间。 机会很多,该进程的内存页面已被交换到磁盘。 至少它们不再处于L1或L2缓存中。 在这种情况下,正在卸载的应用程序将所有这些数据和代码页交换回内存中以“释放”在进程终止时由操作系统发布的资源是没有意义的。 这适用于托管甚至某些非托管资源。 只有保留非后台线程的资源必须处置,否则该进程将保持活动状态。

现在,在正常执行过程中,必须正确清理临时资源(如@fezmonkey指出数据库连接,套接字,窗口句柄 )以避免非托管内存泄漏。 这些都是必须处理的事情。 如果你创建了一个拥有一个线程的类(并且拥有我的意思是它创建了它,并且因此负责确保它停止,至少通过我的编码风格),那么这个类很可能必须实现IDisposable并拆除线程Dispose

.NET框架使用IDisposable接口作为信号,甚至警告开发人员必须处理此类。 I can't think of any types in the framework that implement IDisposable (excluding explicit interface implementations) where disposal is optional.


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

注意 :什么是非托管资源 ? 如果您在Microsoft .NET Framework中找到它:它是托管的。 如果你自己去了解MSDN,那么它是非托管的。 任何你使用过P / Invoke调用的东西都不在.NET框架中可用的所有可用的舒适的世界之外,而且你现在负责清理它。

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

public void Cleanup()

public void Shutdown()

相反,这种方法有一个标准化的名称:

public void Dispose()

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

public interface IDisposable
{
   void Dispose()
}

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

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

你完成了。 除非你能做得更好。

如果你的对象已经分配了一个250MB的System.Drawing.Bitmap (即.NET管理的Bitmap类)作为某种帧缓冲区? 当然,这是一个托管的.NET对象,垃圾收集器将释放它。 但是,你真的想离开250MB的内存吗 - 等待垃圾收集器最终来到并释放它? 如果有一个开放的数据库连接会怎么样? 当然,我们不希望这个连接处于打开状态,等待GC完成对象。

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

所以现在我们会:

  • 摆脱非托管资源(因为我们必须)和
  • 摆脱管理资源(因为我们希望有所帮助)

所以让我们更新我们的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()会怎么样? 然后他们会泄露一些非托管资源!

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

如果这个人忘了叫Dispose() ,我们仍然可以保存他们的培根! 我们仍然有一种方法可以他们调用它:当垃圾回收器最终获得释放(即最终化)我们的对象时。

注意:垃圾收集器最终将释放所有管理对象。 当它发生时,它会调用对象上的Finalize方法。 GC不知道或关心Dispose方法。 这只是我们选择的一个名称,当我们想要摆脱不受管理的东西时,我们称之为方法。

垃圾收集器破坏我们的对象是释放这些烦人的非托管资源的最佳时机。 我们通过重写Finalize()方法来做到这一点。

注意:在C#中,您没有显式重写Finalize()方法。 您编写一个看起来像 C ++析构函数的方法,编译器将其作为Finalize()方法的实现:

~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”,但是传统上称之为Dispose(Boolean)

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
}

注意 :如果您的对象从实现Dispose对象下降,那么当您重写Dispose时,请不要忘记调用它们的基本 Dispose方法:

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

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

如果用户在对象上调用Dispose() ,则所有内容都已清理完毕。 稍后,当垃圾收集器出现并调用Finalize时,它将再次调用Dispose

这不仅浪费,而且如果你的对象有垃圾引用,这些对象是你从上次调用Dispose()已经处理过的对象,那么你将尝试再次处理它们!

你会注意到在我的代码中我很小心地移除了我已经Dispose的对象的引用,所以我不试图在垃圾对象引用上调用Dispose 。 但是这并没有阻止一个微妙的错误蔓延。

当用户调用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() ,我们有:

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

GC运行终结器没有意义 - 一切都照顾好了。

我无法使用Finalize来清理非托管资源吗?

Object.Finalize的文档说:

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

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

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

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

这是你的选择! 但选择Dispose

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

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

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

这个问题是你不知道什么时候垃圾收集器会绕过来完成你的对象。 您的未管理的,不需要的,未使用的本地资源将一直存在,直到垃圾收集器最终运行。 然后它会调用你的终结器方法; 清理非托管资源。 Object.Finalize的文档指出:

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

这是使用Dispose清理非托管资源的优点; 你会知道,并控制,当非托管资源被清理。 他们的破坏是“确定性的”

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

奖金阅读

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

在35页中,他解释了使用二进制对象的问题,并在您的眼前发明COM。 一旦你意识到COM的原因 ,其余的300页是显而易见的,只是详细描述了微软的实施。

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

额外的奖金阅读

当你知道的所有事情都是由Eric Lippert 错误

因此,编写一份正确的终结器确实非常困难, 我可以给你的最好建议是不要尝试


通常使用IDisposable来利用using语句,并利用一种简单的方法对托管对象进行确定性清理。

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

我在使用时遇到了同样的问题

objClient = New AmazonS3Client(AccessKeyID, SecretAccessKeyID)

我解决了使用:

objClient = New AmazonS3Client(AccessKeyID, SecretAccessKeyID, Amazon.RegionEndpoint.USEast1)

(我的s3是USEast1 =美国标准)







c# .net garbage-collection idisposable