繁体   English   中英

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

[英]What happens if I don't call Dispose on the pen object?

如果我不在此代码段中的pen对象上调用Dispose ,会发生什么?

private void panel_Paint(object sender, PaintEventArgs e)
{
    var pen = Pen(Color.White, 1);
    //Do some drawing
}

这里应该做一些修正:

关于Phil Devaney的答案:

“......调用Dispose允许您进行确定性清理,强烈推荐。”

实际上,调用Dispose()并不能确定性地导致.NET中的GC集合 - 即它不会因为您调用Dispose()而立即触发GC。 它只向GC间接发出信号,表示在下一次GC期间可以清除对象(对象所在的生成)。 换句话说,如果对象位于第1代,则在第1代收集发生之前不会将其处理掉。 可以通过编程和确定性地使GC执行集合的唯一方法(尽管不是唯一的方法)是调用GC.Collect()。 但是,建议不要这样做,因为GC会在运行时通过收集有关应用程序运行时内存分配的指标来“调整”自身。 调用GC.Collect()会转储这些指标并导致GC重新开始“调整”。

关于答案:

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

这是不完整的。 由于GC是非确定性的,因此可以使用Dispose Pattern( 如何正确实现Dispose模式 ),以便您可以释放您正在使用的资源 - 托管或非托管。 它与您发布的资源无关 实现Finalizer的需要与您正在使用的资源类型有关 - 即,如果您具有非可终结(即本机)资源,则仅实现一个资源。 也许你会混淆两者。 顺便说一句,你应该避免使用SafeHandle类来实现Finalizer,而不是包装通过P / Invoke或COM Interop封送的本机资源。 如果最终实现了Finalizer,则应始终实现Dispose Pattern。

我还没有看到任何人提到的一个重要注意事项是,如果创建了一次性对象并且它有一个Finalizer(并且你从未真正知道它们是否存在 - 并且你当然不应该对此做出任何假设),那么它将会直接发送到Finalization Queue并进行至少1次额外的GC收集

如果最终未调用GC.SuppressFinalize(),则将在下一个GC上调用该对象的终结器。 请注意,Dispose模式的正确实现应该调用GC.SuppressFinalize()。 因此,如果在对象上调用Dispose()并且它已正确实现了模式,则将避免执行Finalizer。 如果不对具有终结器的对象调用Dispose(),则该对象将在下一个集合上由GC执行其Finalizer。 为什么这么糟糕? CLR中的Finalizer线程(包括.NET 4.6)是单线程的。 想象一下如果你增加这个线程的负担会发生什么 - 你的应用程序性能会告诉你在哪里。

在对象上调用Dispose可提供以下内容:

  1. 减少GC对该过程的压力;
  2. 减少应用程序的内存压力;
  3. 如果LOH(大对象堆)被分割并且对象在LOH上,则减少OutOfMemoryException(OOM)的可能性;
  4. 如果对象具有Finalizer,则将对象保留在Finalizable和f-reachable Queues之外;
  5. 确保清理您的资源(托管和非托管)。

编辑 :我刚刚注意到IDisposable (这里极端讽刺)的“所有知道并且始终正确”的MSDN文档确实说

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

任何人都应该知道,MSDN远非正确,从未提及或显示“最佳实践”,有时提供不编译的示例等。不幸的是,这些都记录在这些文字中。 但是,我知道他们想说的是:在一个完美的世界里,GC会为你清理所有管理资源(多么理想化); 然而,它不会清理非托管资源。 这绝对是真的。 话虽这么说,生活并不完美,也没有任何应用。 GC只会清理没有rooted-references的资源。 这主要是问题所在。

在.NET可以“泄漏”(或不释放)内存的大约15-20种不同方式中,如果不调用Dispose(),最有可能咬你的方法是取消注册/取消挂起/取消/拆除事件处理器/代表。 如果您创建一个具有与其连接的委托的对象,并且您没有在其上调用Dispose()(并且不自行分离委托),则GC仍会将该对象视为具有root权限的引用 - 即委托。 因此,GC永远不会收集它。

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

我有一篇关于我建议使用的Dispose模式的博客文章 - ( 如何正确实现Dispose模式 )。 有些时候你应该删除引用,它永远不会伤害这样做。 实际上,这样做会在GC运行之前做一些事情 - 它会删除对该对象的root权限引用。 GC稍后会扫描其根植入的集合,并收集那些没有根参考的参考。 想想这个例子,当这样做是好的时候:你有一个类型为“ClassA”的实例 - 我们称之为'X'。 X包含一个“ClassB”类型的对象 - 让我们称之为'Y'。 Y实现了IDisposable,因此,X应该做同样的事情来处理Y.假设X在第2代或LOH中,Y在第0代或第1代。当在X上调用Dispose()并且该实现为空时引用Y,立即删除对Y的有根引用。 如果Gen 0或Gen 1发生GC,则清除Y的内存/资源,但X的内存/资源不是因为X存在于Gen 2或LOH中。

Pen将通过GC在未来某个不确定的点进行收集,你是否不叫Dispose

但是,笔不会清除笔所持有的任何非托管资源(例如,GDI +句柄)。 GC仅清理托管资源。 调用Pen.Dispose可以确保及时清理这些非托管资源,并确保您不会泄漏资源。

现在,如果Pen有一个终结器并且终结器清理了非托管资源,那么当Pen被垃圾收集时,那些所说的非托管资源将被清除。 但重点是:

  1. 您应该显式调用Dispose以便释放非托管资源,并且
  2. 如果有终结器并且它清理了非托管资源,您不必担心实现细节。

Pen实现IDisposable IDisposable用于处理非托管资源。 这是.NET中的模式。

有关此主题的先前评论,请参阅此答案

在将来某些不确定的时间,即Pen对象被垃圾收集并调用对象的终结器时,底层GDI +笔柄将不会被释放。 这可能不会在流程终止之前,或者可能更早,但重点是它是非确定性的。 调用Dispose允许您进行确定性清理,强烈建议使用。

如果您真的想知道当您不在图形对象上调用Dispose时有多糟糕,可以使用CLR Profiler,可在此处免费下载 在安装文件夹(默认为C:\\ CLRProfiler)中是CLRProfiler.doc,它有一个很好的例子,说明当你不在Brush对象上调用Dispose时会发生什么。 这很有启发性。 简短的版本是图形对象占用了比你想象的更大的内存块,它们可以长时间闲置,除非你在它们上面调用Dispose。 一旦对象不再使用,系统最终将清理它们,但是当您完成对象时刚刚调用Dispose时,该进程占用的CPU时间更多。 您可能还想阅读此处此处使用IDisposable。

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

图形使用笔和其他对象,这些对象实际上是“非常”昂贵的操作系统对象。 (您可以将笔换成1000x1000位图文件)。 一旦调用特定的清理功能,这些OS对象就会从OS内存中删除。 当您调用它们时,Pen和Bitmap Dispose函数会立即为您执行此操作。

如果您不调用Dispose,垃圾收集器将在未来的某个地方进行清理*。 (它实际上会调用可能调用Dispose()的析构函数/ finalize代码)

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

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

取决于它是否实现了终结器并且在其finalize方法上调用Dispose。 如果是这样,手柄将在GC发布。

如果没有,句柄将保持不变,直到进程终止。

有图形的东西,它可能是非常糟糕的。

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

如果您不处理某些图形对象,这个数字将继续升高和提高。

在旧版本的Windows中,这可能导致整个应用程序崩溃(据我记得限制为10000),虽然不确定Vista / 7但它仍然是一件坏事。

垃圾收集器无论如何都会收集它但是它很重要:如果你不打电话处理你不使用它的物体,它将在记忆中存活更长时间并被提升到更高代,这意味着收集它的成本更高。

在我的脑海里,第一个想法来到表面的是,一旦方法完成执行,这个对象将被处置!我不知道我在哪里得到这个信息!是不是?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM