通过阅读Microsoft文档,我知道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包含一百万个字符串,并且您想立即释放该内存,而不是等待垃圾回收器。上面的代码能做到吗?

评论

我喜欢被接受的答案,因为它告诉您使用IDisposable的正确“模式”,但是就像OP在他的编辑中说的那样,它无法回答他的预期问题。 IDisposable不会“调用” GC,而只是“标记”一个对象为可破坏对象。但是,“立即”释放内存而不是等待GC启动的真正方法是什么?我认为这个问题值得更多讨论。

IDisposable不标记任何内容。 Dispose方法可以执行清理实例使用的资源的操作。这与GC无关。

@约翰。我确实了解IDisposable。这就是为什么我说被接受的答案不能回答OP关于IDisposable是否有助于释放内存的预期问题(以及后续编辑)。由于IDisposable与释放内存无关,因此仅释放资源,就像您所说的那样,根本不需要将托管引用设置为null,这就是OP在他的示例中所做的。因此,对他的问题的正确答案是“不,它不会帮助更快地释放内存。实际上,它根本无法帮助释放内存,只有资源”。但是无论如何,谢谢您的投入。

@desigeek:如果是这种情况,那么您不应该说“ IDisposable不'调用'GC,它只是'标记'一个对象为可破坏对象”

@desigeek:没有确定的释放内存的保证方法。您可以调用GC.Collect(),但这是一个礼貌的请求,而不是需求。必须暂停所有正在运行的线程以进行垃圾回收-如果您想了解更多信息,请阅读.NET安全点的概念。 msdn.microsoft.com/zh-CN/library/678ysw69(v=vs.110).aspx。如果无法暂停线程,例如因为存在对非托管代码的调用,所以GC.Collect()可能根本不起作用。

#1 楼

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


注意:什么是非托管的资源?如果您在Microsoft .NET Framework中找到它:它是受管理的。如果您自己四处逛逛MSDN,则不受管理。您曾经使用P / Invoke调用来摆脱.NET Framework中所有可用内容的舒适环境的任何内容都是不受管理的-现在您要负责清理它。


您创建的对象需要公开一些外界可以调用的方法,以清理非托管资源。可以根据自己的喜好命名该方法:

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()都调用third(!)方法;在其中传递一个布尔值说是否从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 override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}


一切都很好,但是您可以做得更好!


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

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

您会在我的代码中注意到,我小心地删除了对已处置对象的引用,因此,我不会尝试在垃圾对象引用上调用Dispose。但这并不能阻止潜入的细微错误。

当用户调用Dispose()时:CursorFileBitmapIconServiceHandle句柄被破坏。稍后当垃圾收集器运行时,它将尝试再次破坏相同的句柄。

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


解决此问题的方法是告诉垃圾收集器不需要它麻烦最终确定对象-它的资源已经清理完毕,不需要进行任何工作。您可以通过在GC.SuppressFinalize()方法中调用Dispose()来执行此操作:

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


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


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


这是使用Dispose清理非托管资源的优点;您将了解并控制何时清理非托管资源。它们的破坏是“确定性的”。


要回答您的原始问题:为什么现在不释放内存,而不是因为GC决定这样做就释放内存?我有一个面部识别软件,由于不再需要它们,现在需要删除530 MB的内部图像。当我们不这样做时:机器会陷入停顿状态。

奖金阅读

对于任何喜欢此答案样式的人(解释原因,因此如何成为显而易见),建议您阅读Don Box基本COM的第一章:


直接链接:Pearson Publishing的第1章示例

磁铁:84bf0b960936d677190a2be355858e80ef7542c0

在35页中,他解释了使用二进制对象的问题,并在您眼前发明了COM。一旦了解了COM的原因,剩下的300页就显而易见了,并且只详细介绍了Microsoft的实现。

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

额外奖金阅读

当你知道的一切都是埃里克·利珀特(Eric Lippert)的错时


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


评论


您可以做得更好-您需要在Dispose中添加对GC.SuppressFinalize()的调用。

–底座
09年2月12日在21:29

@Daniel Earwicker:是的。 Microsoft希望您完全停止使用Win32,并坚持使用可抽象,可移植,独立于设备的.NET Framework调用。如果您想在下面浏览操作系统;因为您认为自己知道正在运行的操作系统:就是将自己的生命掌握在自己手中。并非每个.NET应用程序都在Windows或台式机上运行。

–伊恩·博伊德(Ian Boyd)
2010年4月2日,12:53

这是一个很好的答案,但是我认为对于标准情况以及该类是从已经实现Dispose的基类派生的情况而言,它将受益于最终代码清单。例如,也已经在这里阅读了(msdn.microsoft.com/zh-cn/library/aa720161%28v=vs.71%29.aspx),我对于从已经实现Dispose的类派生时应该做什么感到困惑(嘿,我是新来的。

–integra753
2012年2月9日在12:42



@GregS等:通常,我不会打扰将引用设置为null。首先,这意味着您不能将它们设置为只读,其次,您必须执行非常丑陋的!= null检查(如示例代码中所示)。您可以设置一个标志,但是不理会它更容易。 .NET GC具有足够的侵略性,因此对字段x的引用在通过x.Dispose()行时将不再被“使用”。

– porges
2012-2-22在6:52

在您提到的Don Box的书的第二页中,他使用了搜索算法的O(1)实现示例,该示例的“细节留给读者练习”。我笑了。

– wip
13-10-29在7:52

#2 楼

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.");
        }
    }
}


评论


我个人喜欢,但是它并没有真正适应框架设计准则。

–mqp
09年2月11日在18:22

我认为它是正确的设计,因为它使确定性范围和范围构造/清除变得容易,尤其是在以复杂的方式与异常处理,锁定和非托管资源使用块混合使用时。该语言将此功能作为一流的功能提供。

– yfeldblum
09年2月11日在18:30

它并不完全遵循FDG中指定的规则,但是它肯定是该模式的有效用法,因为它是“ using语句”所必需的。

–斯科特·多曼(Scott Dorman)
09年2月11日在18:44

只要Log.Outdent不抛出,就绝对没有错。

–丹尼尔(Daniel Earwicker)
09年2月11日在19:51

使用IDisposable和“使用”作为获取“范围内的行为”以确保异常安全的一种手段是否滥用?进一步说明为什么不同的人喜欢/不喜欢这种技术。这有点争议。

–布赖恩
17-10-13在14:46



#3 楼

Dispose模式的目的是提供一种清除托管和非托管资源的机制,何时发生取决于调用Dispose方法的方式。在您的示例中,使用Dispose实际上并没有执行任何与Dispose相关的操作,因为清除列表不会对该处置的集合产生影响。同样,将变量设置为null的调用也不会影响GC。

您可以看一下本文,以获取有关如何实现Dispose模式的更多详细信息,但基本上看起来像this:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}


这里最重要的方法是Dispose(bool),它实际上在两种不同的情况下运行:


dispose == true:该方法已由用户代码直接或间接调用。可以处理托管和非托管资源。
dispose == false:运行时已从终结器内部调用了该方法,因此您不应引用其他对象。只能处置非托管资源。

简单地让GC负责清理的问题是您无法真正控制GC何时运行收集周期(可以调用GC.Collect(),但实际上不应),因此资源的停留时间可能比需要的时间长。请记住,调用Dispose()实际上并不会导致收集周期或以任何方式导致GC收集/释放对象。它只是提供了一种方法,可以更确定地清除所使用的资源,并告诉GC已经执行了该清理。

IDisposable和dispose模式的全部要点不是立即释放内存。实际上,对Dispose的调用实际上甚至唯一有机会立即释放内存的时间是在处理Dispose == false方案并处理非托管资源时。对于托管代码,在GC运行收集周期之前实际上不会回收内存,您实际上无法对其进行控制(除了调用GC.Collect(),我已经提到过这不是一个好主意)。

由于.NET中的字符串不使用任何未使用的资源并且不实现IDisposable,因此您的方案实际上不是有效的,没有办法强制对其进行“清理”。 />

评论


您是否忘了实现终结器?

–布达
2011年7月2日,下午5:19

@Budda:不,他正在使用SafeHandle。不需要析构函数。

–汉克·霍尔特曼
2011年11月2日,11:16

+1用于为多次调用Dispose()添加安全网。规范说,多次通话应该是安全的。太多的Microsoft类无法实现该目标,并且您会收到烦人的ObjectDisposedException。

–杰西·奇斯霍尔姆(Jesse Chisholm)
2012年8月30日23:34

但是Dispose(布尔处理)是您自己的SimpleCleanup类上的方法,并且框架永远不会调用它。由于仅使用“ true”作为参数来调用它,因此“ dispose”将永远不会为假。您的代码与IDisposable的MSDN示例非常相似,但缺少@@ Budda指出的终结器,这是使用dispose = false进行调用的地方。

– yoyo
15年5月13日在22:11

#4 楼

在调用Dispose之后,不应再调用对象的方法(尽管对象应允许对Dispose的进一步调用)。因此,问题中的例子很愚蠢。如果调用了Dispose,则可以丢弃对象本身。因此,用户应该只丢弃对整个对象的所有引用(将它们设置为null),并且该对象内部的所有相关对象将自动清除。

关于托管/非托管和托管的一般问题在其他答案的讨论中,我认为对这个问题的任何答案都必须以非托管资源的定义开始。

归根结底是有一个可以调用的函数来放置系统进入一个状态,您可以调用另一个函数将其从该状态恢复。现在,在典型示例中,第一个可能是返回文件句柄的函数,第二个可能是对CloseHandle的调用。

但是-这是关键-它们可能是任何匹配的功能对。一个建立一个状态,另一个将其拆除。如果状态已建立但尚未拆除,则该资源的实例存在。您必须安排在正确的时间进行拆卸-资源不是由CLR管理的。唯一自动管理的资源类型是内存。有两种:GC和堆栈。值类型由堆栈(或通过引用类型内的游标)管理,而引用类型由GC管理。

这些函数可能会导致状态更改,可以自由交织,或者可能需要完美嵌套。状态更改可能是线程安全的,也可能不是线程安全的。

看看大法官的问题中的示例。对日志文件的缩进的更改必须完美嵌套,否则一切都会出错。而且它们不太可能是线程安全的。

可以与垃圾收集器搭便车,以清理您的非托管资源。但是,仅当状态更改函数是线程安全的并且两个状态可以具有以任何方式重叠的生存期时,才可以。因此,Justice的资源示例不能包含终结器!对于任何种类的资源,您都可以实现IDisposable,而无需使用终结器。终结器绝对是可选的-必须如此。在许多书中甚至没有提到它。

然后,您必须使用using语句来确保Dispose被调用。从本质上讲,这就像在堆栈上搭个顺风车(例如,终结器到达GC,using到达堆栈)。

缺少的部分是,您必须手动编写Dispose并将其调用到您的字段和基类。 C ++ / CLI程序员不必这样做。在大多数情况下,编译器都会为它们编写代码。与无法抗拒将终结器添加到实现IDisposable的每个类中的人进行争论)。

您无需编写类,而是编写了一个函数。该函数接受一个委托来回调:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}


然后一个简单的示例将是:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");


传入的lambda充当代码块,因此就像您自己构造控制结构以实现与using相同的目的一样,不同之处在于您不再有调用方滥用它的危险。他们没有办法清理资源。

如果资源是可能具有重叠生命周期的资源,则此技术的用处不大,因为那样一来,您便希望能够先构建资源A,然后构建资源B,然后终止资源A,再终止资源B。如果您已强迫用户像这样完美嵌套。但是然后您需要使用IDisposable(除非仍然实现了终结器,除非您已经实现了线程安全,这不是免费的。)。

评论


回复:“在对象上调用Dispose之后,不应再进一步调用该对象的方法”。 “应该”是有效词。如果您有待处理的异步操作,则它们可能在您的对象被处置后进入。导致ObjectDisposedException。

–杰西·奇斯霍尔姆(Jesse Chisholm)
2012年8月30日23:37

除了我的问题,您的问题似乎是唯一的答案,它提出了非托管资源封装了GC无法理解的状态的想法。但是,非托管资源的一个关键方面是,即使状态“拥有”该资源的对象没有,状态可能需要清理其状态的一个或多个实体也可以继续存在。您如何看待我的定义?十分相似,但我认为它使“资源”变得有点名词化(这是外部对象更改其行为的“协议”,以换取不再需要其服务的通知)

–超级猫
13年4月26日在15:43


@DanielEarwicker:有趣的文章,尽管我可以想到至少您真正没有涉及到的一种非托管资源:对来自长寿对象的事件的订阅。事件订阅是可互换的,但是即使内存是无限的,处理它们失败也可能代价高昂。例如,一个允许在枚举期间进行修改的集合的枚举器可能需要订阅该集合的更新通知,并且一个集合在其生命周期中可能会多次更新。如果枚举数被放弃而没有取消订阅...

–超级猫
13年4月26日在21:12

进入和退出这对操作是我如何看待资源的核心。订阅/取消订阅事件应该毫无困难。就正交/可替代特性而言,实际上与内存泄漏是无法区分的。 (这并不奇怪,因为订阅只是将对象添加到列表中。)

–丹尼尔(Daniel Earwicker)
13年4月26日在21:39

#5 楼

我使用IDisposable的方案:清理非托管资源,取消订阅事件,关闭连接

我用于实现IDisposable的习惯用法(不是线程安全的):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}


评论


完整的模式说明可以在msdn.microsoft.com/zh-cn/library/b1yfkh5e.aspx中找到

–LicenseQ
09年2月11日在20:41

除非您拥有不受管理的资源,否则永远不要包含终结器。即使这样,首选的实现方式还是将非托管资源包装在SafeHandle中。

–戴夫·布莱克
17年2月17日在19:20

#6 楼

如果无论如何将要对MyCollection进行垃圾收集,那么您就不需要处理它。这样做只会使CPU过度浪费,甚至可能使垃圾收集器已经执行的某些预先计算的分析无效。

我使用IDisposable来执行诸如确保正确处置线程之类的操作。具有不受管理的资源。

编辑针对Scott的评论:


唯一影响GC性能指标的是调用[sic] GC的时间。从概念上讲,GC维护对象引用图的视图以及所有来自线程堆栈框架的对其的引用。该堆可能很大,并且为了优化,GC缓存了对不太可能经常更改的页面的分析,以避免不必要地重新扫描该页面。当页面中的数据发生更改时,GC会从内核接收通知,因此它知道页面脏了,需要重新扫描。如果集合位于Gen0中,则页面中的其他内容很可能也会改变,但这在Gen1和Gen2中不太可能发生。有趣的是,对于那些为了将Silverlight插件在该平台上工作而将GC移植到Mac的团队来说,这些挂钩在Mac OS X中不可用。

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

现在,在正常执行期间,必须正确清除临时资源(如@fezmonkey指出数据库)连接,套接字,窗口句柄),以避免非托管内存泄漏。这些都是必须处理的事情。如果您创建某个拥有线程的类(并拥有,我的意思是它创建了它,因此至少通过我的编码风格负责确保它停止),那么该类很可能必须实现IDisposable并在执行过程中拆除该线程Dispose .NET框架使用IDisposable接口作为信号,甚至警告开发人员必须丢弃此类。我想不出框架中实现IDisposable的任何类型(不包括显式接口实现),其中处置是可选的。

评论


调用Dispose是完全有效,合法和鼓励的。实现IDisposable的对象通常这样做是有原因的。仅当调用GC.Collect()时,才会影响GC性能指标。

–斯科特·多曼(Scott Dorman)
09年2月11日在21:17

对于许多.net类,处置是“某种”可选的,这意味着“通常”放弃实例不会造成任何麻烦,只要人们不会疯狂地创建并放弃新实例即可。例如,编译器生成的控件代码似乎在实例化控件时创建字体,而在处理表单时放弃字体;如果创建并部署了数千个控件,则可能会占用数千个GDI句柄,但是在大多数情况下,控件的创建和销毁程度并没有那么大。尽管如此,仍然应该设法避免这种放弃。

–超级猫
2011年8月2日15:14



对于字体,我怀疑问题在于Microsoft从未真正定义哪个实体负责处理分配给控件的“字体”对象。在某些情况下,控件可能与寿命更长的对象共享字体,因此让控件“处置字体”会很糟糕。在其他情况下,字体将被分配给控件,而其他地方则没有,因此,如果控件不进行处理,则没人会分配。顺便说一句,如果有一个单独的非一次性FontTemplate类,则可以避免字体的这种困难,因为控件似乎不使用其Font的GDI句柄。

–超级猫
2011年8月2日在15:23

#7 楼

是的,该代码是完全冗余且不必要的,并且不会使垃圾回收器执行任何其他本来不会做的事情(一旦MyCollection的实例超出范围,那就是。)尤其是.Clear()调用。

回答您的修改:有点。如果执行此操作:

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


在内存管理上,此功能与之相同:

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


如果确实真的需要立即释放内存,请致电GC.Collect()。不过,这里没有理由这样做。需要时将释放内存。

评论


回复:“内存将在需要时释放。”而是说,“当GC决定需要时。”在GC决定确实需要内存之前,您可能会看到系统性能问题。现在释放它可能不是必需的,但可能很有用。

–杰西·奇斯霍尔姆(Jesse Chisholm)
2012年8月30日23:40

在某些极端情况下,在集合中取消引用可能会加速对其所引用项目的垃圾回收。例如,如果创建了一个大型数组并填充了对较小的新创建项的引用,但是此后很长时间都不需要,则放弃该数组可能会使这些项保留到下一个2级GC,而先将其清零可能会使这些项目符合下一个0级或1级GC的条件。可以肯定的是,无论如何,在大型对象堆上放置大型的短期对象还是很棘手的(我不喜欢这种设计),但是...

–超级猫
13年4月26日在15:48

...在放弃这些数组之前将它们清零,有时会减少GC的影响。

–超级猫
13年4月26日在15:49

#8 楼

在您发布的示例中,它仍然不会“立即释放内存”。所有内存都是垃圾回收的,但是它可以允许在较早的一代中回收内存。您必须进行一些测试才能确定。


《框架设计指南》是指南,而不是规则。他们告诉您接口的主要用途,何时使用,何时使用以及何时不使用。

我曾经读过一个代码,该代码在使用IDisposable失败时只是一个简单的RollBack() 。下面的MiniTx类将检查Dispose()上的标志,如果Commit调用从未发生,它将自行调用Rollback。它增加了一个间接层,使调用代码更容易理解和维护。结果看起来像:

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

    tx.Commit();
} 


我还看到计时/日志记录代码执行相同的操作。在这种情况下,Dispose()方法停止了计时器并记录了该块已退出。

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


因此,这里有一些具体示例,它们不执行任何非托管资源清理,但确实成功使用IDisposable创建了更干净的代码。

评论


看一下使用高阶函数的@Daniel Earwicker的示例。对于基准测试,计时,日志记录等。这似乎更加简单。

– Aluan Haddad
16-09-22在3:15

#9 楼

如果要立即删除,请使用非托管内存。

请参阅:


Marshal.AllocHGlobal
Marshal.FreeHGlobal
Marshal。 DestroyStructure


#10 楼

我不会重复关于使用或释放​​非托管资源的常见内容,所有内容已涵盖。但我想指出什么是常见的误解。
给出以下代码

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


我意识到Disposable实现不遵循当前准则,但是希望大家都知道。
现在,调用Dispose时会释放多少内存?

答案:无。
调用Dispose可以释放非托管资源,但不能回收托管内存,只有GC可以做到。并不是说上述不是一个好主意,遵循上述模式实际上仍然是一个好主意。一旦运行Dispose,即使LargeStuff实例可能仍在范围内,也不会停止GC回收_Large正在使用的内存。 _Large中的字符串也可能是第0代,但是LargeStuff的实例可能是第2代,因此,再次声明,将更快地回收内存。
添加终结器来调用上面显示的Dispose方法毫无意义。虽然。那只会延迟对内存的回收,以允许终结器运行。

评论


如果LargeStuff实例的时间足够长,可以进入第2代,并且_Large持有对第0代中新创建的字符串的引用,那么如果LargeStuff实例被放弃而没有使_Large无效,则_Large引用的字符串将保留到下一个Gen2集合。将_Large清零可能会使字符串在下一个Gen0集合中被消除。在大多数情况下,取消引用是没有帮助的,但是在某些情况下,引用可以带来一些好处。

–超级猫
2013年5月9日23:10

#11 楼

除了其主要用途是控制系统资源生存期的一种方法(完全由Ian的出色回答,功劳!)之外,IDisposable / using组合还可以用于确定(关键)全局资源的状态变化:控制台,线程,进程,任何全局对象(如应用程序实例)。

我写了一篇有关此模式的文章:http://pragmateek.com/c-scope-your-global状态可更改和使用状态声明/

它说明了如何以可重用和可读的方式保护某些常用的全局状态:控制台颜色,当前线程的文化, Excel应用程序对象属性...

#12 楼

如果有的话,我希望代码的效率不如省掉代码。

不需要调用Clear()方法,如果Dispose没有,GC可能不会这样做。做到...

#13 楼

在示例代码中,Dispose()操作所做的某些事情可能会由于MyCollection对象的正常GC而不会产生影响。

如果_theList_theDict引用的对象被其他对象引用,则List<>Dictionary<>对象将不会被收集,但突然将没有内容。如果没有如示例中的Dispose()操作,则这些集合仍将包含其内容。

当然,如果是这种情况,我将其称为损坏的设计-我只是指出(据我所知,很显然,Dispose()操作可能不是完全多余的,具体取决于片段中未显示的List<>Dictionary<>的其他用途。

评论


它们是私有字段,因此我认为可以假设OP没有提供对它们的引用是公平的。

–mqp
09年2月11日在21:20

1)代码片段只是示例代码,所以我只是指出可能会有容易忽略的副作用; 2)私有字段通常是getter属性/方法的目标-可能太多了(某些人认为getter / setter有点反模式)。

– Michael Burr
09年2月11日在22:27

#14 楼

关于“非托管资源”的大多数讨论中的一个问题是,它们实际上并未定义该术语,但似乎暗示它与非托管代码有关。虽然确实有许多类型的非托管资源都可以与非托管代码交互,但是以这种术语来思考非托管资源并没有帮助。

相反,应该认识到所有托管资源的共同点:它们所有这些都牵涉到一个对象,要求某个外部“事物”代表它做某事,从而损害其他“事物”,而另一实体同意这样做,直到另行通知。如果物体被遗弃并消失得无影无踪,那么没有任何东西可以告诉外界“事物”,它不再需要代表不存在的物体改变其行为。因此,“事物”的有用性将永久降低。

然后,不受管理的资源表示某些外部“事物”同意以某个对象的名义更改其行为,这将无用地削弱其有用性如果物体被抛弃并不再存在,则在“事物”之外。受管资源是该协议的受益人,但已签名以接受通知(如果该通知被放弃),并将在销毁该通知之前使用该通知将其事务整理。

评论


嗯,IMO,非托管对象的定义很明确;任何非GC对象。

–尼尔
2014年5月26日21:30

@Eonil:非托管对象!=非托管资源。事件之类的事件可以完全使用托管对象来实现,但仍然构成非托管资源,因为–至少在短期对象订阅了长期对象的事件的情况下– GC对于如何清除它们一无所知。

–超级猫
2014年5月27日13:35

#15 楼

IDisposable非常适合取消订阅事件。

#16 楼

首先定义。对我来说,非托管资源意味着某个类,该类实现IDisposable接口或使用对dll的调用创建的某些类。 GC不知道如何处理此类对象。如果类仅具有例如值类型,那么我不认为此类是具有不受管资源的类。
对于我的代码,我遵循以下做法:


如果由...创建如果我的班级使用了一些非托管资源,那么这意味着我还应该实现IDisposable接口以清理内存。

我完成使用后,立即清洁对象。

在我的dispose方法中,我遍历类的所有IDisposable成员并调用Dispose。

在我的Dispose方法中,调用GC.SuppressFinalize(this)以便通知垃圾收集器我的物体已经被清理了。我这样做是因为调用GC是一项昂贵的操作。

作为额外的预防措施,我尝试多次调用Dispose()。

有时我添加私有成员_disposed和检入方法调用是否清除了对象。如果将其清除,则生成ObjectDisposedException

以下模板演示了我用文字描述的代码示例:





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


评论


“对我来说,非托管资源意味着某个类,该类实现IDisposable接口或通过使用dll调用创建的某个类。”因此,您是说IDisposable的任何类型本身应该被视为非托管资源?这似乎不正确。同样,如果实现类型是纯值类型,则您似乎建议不需要处理它。这似乎也是错误的。

– Aluan Haddad
16-09-22在3:08

每个人都自己判断。我不喜欢为了添加而在我的代码中添加一些内容。这意味着,如果添加IDisposable,则意味着我创建了某种GC无法管理的功能,或者我想它将无法正确管理其生命周期。

–尤里(Yuriy Zaletskyy)
18年4月20日在22:22

#17 楼

给定的代码示例不是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.
    }
}


#18 楼

处置托管资源最合理的用例是为GC准备回收本来就不会收集的资源。

一个主要的例子是循环引用。

虽然最佳做法是使用避免循环引用的模式,但是如果您最终得到(例如)一个具有引用返回其“父”引用的“子”对象,则可以停止GC如果您只是放弃引用而依赖于GC,则可以收集父项的集合-如果您已经实现了终结器,则永远不会调用它。

唯一的解决方法是通过将子级上的Parent引用设置为null来手动中断循环引用。

在父母和孩子身上实施IDisposable是最好的方法。在父对象上调用Dispose时,请在所有子对象上调用Dispose,然后在子对象Dispose方法中,将Parent引用设置为null。

评论


在大多数情况下,GC不能通过识别死物来工作,而可以通过识别活物来工作。在每个gc周期之后,对于已注册完成的每个对象,都将其存储在大对象堆中,或者是活动的WeakReference的目标,系统将检查一个标志,该标志指示在上一个GC中找到了活动的根引用循环,或者将对象添加到需要立即完成的对象队列中,从大对象堆中释放对象,或者使弱引用无效。如果不存在其他引用,则循环引用不会使对象保持活动状态。

–超级猫
16 Sep 15'2:39



#19 楼

我看到很多答案已经转移到谈论将IDisposable用于托管和非托管资源。我建议将本文作为对IDisposable应如何实际使用的最佳解释之一。

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

对于实际问题;如果您使用IDisposable清理占用大量内存的托管对象,那么简短的回答是“否”。原因是,一旦处置IDisposable,就应该让它超出范围。届时,所有引用的子对象也将超出范围,并将被收集。

唯一真正的例外是,如果您在托管对象中占用了大量内存,并且阻塞了该线程,等待某些操作完成。如果在该调用完成后不需要使用那些对象,则将这些引用设置为null可能会使垃圾收集器更快地收集它们。但是这种情况将代表需要重构的错误代码,而不是IDisposable的用例。

评论


我不明白为什么有人在您的答案中加上-1

–塞巴斯蒂安·奥斯卡·洛佩兹(Sebastian Oscar Lopez)
19年8月23日在13:12