代码之家  ›  专栏  ›  技术社区  ›  Andreas Brinck

如果我不调用笔对象上的Dispose,会发生什么?

  •  34
  • Andreas Brinck  · 技术社区  · 14 年前

    如果我不打电话怎么办 Dispose pen 此代码段中的对象?

    private void panel_Paint(object sender, PaintEventArgs e)
    {
        var pen = Pen(Color.White, 1);
        //Do some drawing
    }
    
    10 回复  |  直到 14 年前
        1
  •  23
  •   Community CDub    7 年前

    这个 Pen 无论您是否打电话给GC,都将在将来某个不确定的时间收集 Dispose .

    但是,PEN持有的任何非托管资源(例如,gdi+句柄)将不会被GC清除。GC只清理托管资源。打电话 Pen.Dispose 允许您确保及时清理这些非托管资源,并且不会泄漏资源。

    现在,如果 有一个终结器,该终结器清除非托管资源,然后当 是垃圾收集。但关键是:

    1. 你应该打电话 处置 显式地释放非托管资源,以及
    2. 如果有终结器并且它清理了非托管资源,则不需要担心实现细节。

    工具 IDisposable . 可识别 用于处理非托管资源。这是.NET中的模式。

    有关此主题的先前评论,请参阅 answer .

        2
  •  26
  •   Corio    7 年前

    这里应该做一些修正:

    关于菲尔·德瓦尼的回答:

    “…调用Dispose允许您进行确定性清理,强烈建议您这样做。”

    实际上,调用Dispose()并不能确定地导致.NET中的GC集合,即它不会立即触发GC,因为您调用了Dispose()。它只是间接地向GC发出信号,表示对象可以在下一个GC(用于对象所在的生成)期间被清除。换言之,如果对象位于Gen1中,那么在Gen1集合发生之前,它不会被释放。通过调用gc.collect()可以以编程方式和确定性方式导致gc执行集合的唯一方法之一(尽管不是唯一的方法)。但是,不建议这样做,因为GC在运行时通过收集应用程序运行时内存分配的度量来“调优”自身。调用gc.collect()将转储这些度量并使gc重新开始其“调优”。

    关于答案:

    IDisposable用于处理 非受管的 资源。这是.NET中的模式。

    这是不完整的。由于GC是非确定性的,因此释放模式( How to properly implement the Dispose pattern ,以便释放正在使用的资源-托管或非托管。它有 没有什么 处理您正在释放的资源。实现终结器的需要与您正在使用的资源类型有关—即,只有在您具有不可终结(即本机)资源时才实现一个资源。也许你混淆了这两者。顺便说一句,您应该避免使用safehandle类来实现终结器,该类包装通过p/invoke或com interop封送的本机资源。如果最终实现了终结器,那么应该 总是 实现释放模式。

    我还没有看到有人提到的一个关键点是,如果创建了可释放对象,并且它有一个终结器(而且您永远不知道它们是否有——而且您当然不应该对此做任何假设),那么它将直接发送到终结队列,并至少为1个额外的GC集合而生存。 .

    如果最终未调用gc.SuppressFinalize(),则将在下一个gc上调用该对象的终结器。请注意,Dispose模式的正确实现应该调用gc.SuppressFinalize()。因此,如果对对象调用Dispose(),并且它正确地实现了模式,则将避免执行终结器。如果不对具有终结器的对象调用Dispose(),则该对象的终结器将由GC在下一个集合上执行。为什么这样不好?.NET 4.6之前(含)的clr中的终结器线程是单线程的。想象一下如果你增加这个线程的负担会发生什么——你的应用程序性能取决于你知道在哪里。

    对对象调用Dispose提供以下功能:

    1. 减少过程中GC上的应变;
    2. 降低应用程序的内存压力;
    3. 如果LOH(大型对象堆)碎片化且对象位于LOH上,则减少内存不足异常(OOM)的可能性;
    4. 如果对象具有终结器,则使其远离可终结队列和F可到达队列;
    5. 确保以确定的方式清理资源(托管和非托管)。

    编辑 : 我刚刚注意到,关于 IDisposable (这里极度讽刺)确实说

    此接口的主要用途是 释放非托管资源

    正如任何人应该知道的,msdn是远远不正确的,从来没有提到或显示“最佳实践”,有时提供不编译的示例等。不幸的是,这是记录在这些话。然而,我知道他们想说的是:在一个完美的世界里,GC将清理所有 管理 为你提供资源(多么理想化);无论如何清理,它都不会。 非受管的 资源。这是绝对正确的。也就是说,生活并不完美,任何应用也不完美。 GC将只清理没有根引用的资源。 这主要是问题所在。

    在.NET可以“泄漏”(或不释放)内存的大约15-20种不同方法中,如果不调用Dispose(),最有可能咬到您的方法是无法注销/取消挂起/释放/分离事件处理程序/委托。如果您创建了一个对象,该对象上连接了委托,并且没有调用Dispose()(也没有自己分离委托),那么GC仍然会将该对象视为具有根引用,即委托。因此,GC永远不会收集它。

    @下面是Joren的评论/问题(我的回复太长,无法发表评论):

    我有一篇关于建议使用的释放模式的博客文章-( 如何正确实现释放模式 )有时您应该将引用空出来,这样做永远不会有坏处。实际上,这样做会在GC运行之前做一些事情——它会删除对该对象的根引用。GC稍后扫描它的根引用集合,并收集那些没有根引用的引用。当这样做是好的时候,可以考虑这个例子:您有一个“classa”类型的实例,我们称它为“x”。X包含一个“ClassB”类型的对象-我们称之为“Y”。Y实现了IDisposable,因此,x应该对y做同样的处理。假设x在第2代中,或者loh和y在第0代或1代中。当在X上调用Dispose()并且该实现使对Y的引用为空时,对Y的根引用将立即移除。如果GC发生在第0代或第1代,那么Y的内存/资源将被清除,但X的内存/资源不是因为X位于第2代或LOH中。

        3
  •  13
  •   Phil Devaney    14 年前

    在将来某个不确定的时间(即当pen对象被垃圾收集并调用对象的终结器)之前,不会释放底层的gdi+pen句柄。这可能直到进程终止,也可能更早,但关键是它的不确定性。调用Dispose允许您进行确定性清理,强烈建议您这样做。

        4
  •  2
  •   CodingBarfield    14 年前

    正在使用的.NET内存总量是.NET部分+正在使用的所有“外部”数据。操作系统对象、打开的文件、数据库和网络连接都占用了一些不是纯.NET对象的资源。

    图形使用钢笔和其他物体,它们实际上是操作系统的物体,保存起来“相当”昂贵。(您可以将笔换成1000x1000位图文件)。只有在调用特定的清理函数后,这些OS对象才会从OS内存中删除。当调用pen和bitmap dispose函数时,它们会立即为您执行此操作。

    如果您不调用Dispose,垃圾收集器将在“将来的某个地方”来清理它们。 (它实际将调用析构函数/终结代码,该代码可能调用Dispose())

    *在一台内存无限(或超过1GB)的机器上,未来的某个地方可能会非常遥远。在一台什么都不做的机器上,清理那块巨大的位图或非常小的笔可能要花30分钟的时间。

        5
  •  2
  •   Bill W    8 年前

    如果您真的想知道不调用图形对象上的Dispose有多糟糕,可以使用clr profiler,可以免费下载。 here. 在安装文件夹中(默认为c:\clrprofiler)是clrprofiler.doc,它有一个很好的示例,说明当您不调用Brush对象上的Dispose时会发生什么。这很有启发性。您可能还想了解如何使用IDisposable here here .

        6
  •  1
  •   m.edmondson    14 年前

    它将保留资源,直到垃圾收集器清理它为止

        7
  •  1
  •   Aliostad    14 年前

    取决于它是否实现终结器,并且它是否在其Finalize方法上调用Dispose。如果是这样,句柄将在GC上释放。

    否则,句柄将一直保留,直到进程终止。

        8
  •  0
  •   Shadow Wizard    14 年前

    有了图形材料,可能会很糟糕。

    打开Windows任务管理器。单击“选择列”并选择名为“gdi对象”的列。

    如果不处理某些图形对象,此数字将不断增加。

    在旧版本的Windows中,这可能会使整个应用程序崩溃(据我记忆,限制是10000个),尽管不能确定Vista/7,但这仍然是一件坏事。

        9
  •  -1
  •   Ali Tarhini    14 年前

    垃圾收集器无论如何都会收集垃圾,但在以下情况下它很重要: 如果您不调用不使用的对象上的Dispose,它将在内存中存活更长时间,并提升到更高的代,这意味着收集它的成本更高。

        10
  •  -3
  •   Rami Alshareef    14 年前

    在我的脑海中,首先想到的是这个对象将在方法完成执行后立即被处理!,我不知道我从哪里得到这个信息!,对吗?