我知道从the MSDN documentation开始,"primary"接口的"primary"用于清理非托管资源 .
对我来说,"unmanaged"意味着像数据库连接,套接字,窗口句柄等等 . 但是,我已经看到了实现 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
使用的垃圾收集器空闲内存比通常更快?
edit :到目前为止,人们已经发布了一些使用IDisposable清理非托管资源(例如数据库连接和位图)的好例子 . 但是假设上面代码中的 _theList
包含了一百万个字符串,你想现在释放那个内存,而不是等待垃圾收集器 . 上面的代码会实现吗?
19 回答
如果
MyCollection
无论如何都要进行垃圾收集,那么你就不需要处理它了 . 这样做只会使CPU超过必要的流失,甚至可能使垃圾收集器已经执行的一些预先计算的分析无效 .我使用
IDisposable
来做一些事情,比如确保正确处理线程,以及非托管资源 .EDIT 回应斯科特的评论:
从概念上讲,GC维护对象引用图的视图,以及从线程的堆栈帧对它的所有引用 . 这个堆可能非常大并且跨越许多页面的内存 . 作为优化,GC会缓存对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面 . 当页面中的数据发生更改时,GC会从内核接收通知,因此它知道页面很脏并需要重新扫描 . 如果集合在Gen0中,则页面中的其他内容可能也会发生变化,但这在Gen1和Gen2中的可能性较小 . 有趣的是,这些挂钩在Mac OS X中不适用于将GC移植到Mac以便在该平台上运行Silverlight插件的团队 .
反对不必要的资源处置的另一点:想象一个过程正在卸载的情况 . 想象一下,这个过程已经运行了一段时间 . 有可能该进程的许多内存页面已被交换到磁盘 . 至少它们不再处于L1或L2缓存中 . 在这种情况下,卸载的应用程序没有必要将所有这些数据和代码页交换回内存以“释放”操作系统将在进程终止时释放的资源 . 这适用于托管甚至某些非托管资源 . 只有处理非后台线程保持活动的资源才能处理,否则进程将保持活动状态 .
现在,在正常执行期间,必须正确清理短暂的资源(因为@fezmonkey指出数据库连接,套接字,窗口句柄)以避免非托管内存泄漏 . 这些是必须处理的事物 . 如果你创建了一个拥有一个线程的类(并且拥有我的意思是它创建它并因此负责确保它停止,至少通过我的编码风格),那么该类很可能必须实现
IDisposable
并拆除线程期间Dispose
..NET框架使用
IDisposable
接口作为信号,甚至警告开发人员必须处理此类 . 我想不出框架中实现IDisposable
(不包括显式接口实现)的任何类型,其中dispos是可选的 .我不会重复关于使用或释放未管理资源的常规内容,这些内容已全部涵盖 . 但我想指出似乎是一种常见的误解 .
给出以下代码
我意识到Disposable实现不符合当前的指导原则,但希望你们都能理解 .
现在,当调用Dispose时,释放多少内存?
答:没有 .
调用Dispose可以释放非托管资源,它不能回收托管内存,只有GC才能这样做 . 这并不是说以上不是一个好主意,遵循上述模式实际上仍然是一个好主意 . 一旦运行了Dispose,就没有什么能阻止GC重新声明_Large正在使用的内存,即使LargeStuff的实例可能仍在范围内 . _Large中的字符串也可能在gen 0中,但LargeStuff的实例可能是gen 2,因此,内存将更快地被重新声明 .
添加终结器来调用上面显示的Dispose方法没有意义 . 这将只是延迟重新声明内存以允许终结者运行 .
Dispose()
操作在示例代码中执行的操作可能具有由于MyCollection
对象的正常GC而不会发生的效果 . 如果_theList
或_theDict
引用的对象被其他对象引用,那么List<>
或Dictionary<>
对象将不会被收集,但突然没有内容 . 如果示例中没有Dispose()操作,那些集合仍将包含其内容 .当然,如果是这种情况,我会称之为破碎的设计 - 我只是指出(迂腐地,我认为)
Dispose()
操作可能不完全是多余的,这取决于是否还有List<>
或Dictionary<>
的其他用途片段中未显示的内容 .IDisposable
适用于取消订阅活动 .Dispose is 释放非托管资源的重点 . 它需要在某个时刻完成,否则它们将永远不会被清除 . 垃圾收集器不知道 how 在
IntPtr
类型的变量上调用DeleteHandle()
,它不知道 whether 或者它不需要调用DeleteHandle()
.您创建的对象需要公开一些外部世界可以调用的方法,以便清理非托管资源 . 该方法可以任意命名:
要么
但相反,此方法有一个标准化名称:
甚至创建了一个界面
IDisposable
,它只有一个方法:因此,您使对象公开
IDisposable
接口,这样您就可以保证已编写了单一方法来清理非托管资源:而且你已经完成了 . Except you can do better.
如果您的对象已将250MB System.Drawing.Bitmap (即.NET托管的Bitmap类)分配为某种帧缓冲区,该怎么办?当然,这是一个托管的.NET对象,垃圾收集器将释放它 . 但是你真的想留下250MB的内存 - 等待垃圾收集器最终出现并释放它吗?如果有open database connection怎么办?当然,我们不希望该连接处于打开状态,等待GC完成对象 .
如果用户调用
Dispose()
(意味着他们不再计划使用该对象)为什么不摆脱那些浪费的位图和数据库连接?所以现在我们将:
摆脱非托管资源(因为我们必须),和
摆脱托管资源(因为我们希望对您有所帮助)
所以让我们更新我们的
Dispose()
方法来摆脱那些托管对象:一切都很好, except you can do better !
如果该人 forgot 在您的对象上调用
Dispose()
怎么办?然后他们会泄漏一些 unmanaged 资源!如果这个人忘了打电话
Dispose()
,我们仍然可以保存他们的培根!我们仍然有办法为它们调用它:当垃圾收集器最终解决时释放(即完成)我们的对象 .垃圾收集器破坏我们的对象是释放那些讨厌的非托管资源的最佳时机 . 我们通过覆盖
Finalize()
方法来完成此操作 .但是该代码中存在一个错误 . 你看,垃圾收集器在_265598上运行;你不知道两个对象被销毁的顺序 . 完全有可能在你的
Dispose()
代码中,你想要摆脱的 managed 对象(因为你想要有所帮助)不再存在:所以你需要的是
Finalize()
告诉Dispose()
它应该 not touch any managed 资源(因为它们可能不再存在),同时仍然释放非托管资源 .执行此操作的标准模式是
Finalize()
和Dispose()
都调用 third (!)方法;如果您从Dispose()
(而不是Finalize()
)调用它,则传递布尔说法,这意味着释放托管资源是安全的 .这个内部方法可以给出一些任意名称,如"CoreDispose"或"MyInternalDispose",但传统上称之为
Dispose(Boolean)
:但更有用的参数名称可能是:
你呢将
IDisposable.Dispose()
方法的实现更改为:你的终结者:
一切都很好, except you can do better !
如果用户在您的对象上调用
Dispose()
,则所有内容都已清除 . 稍后,当垃圾收集器出现并调用Finalize时,它将再次调用Dispose
.这不仅浪费,而且如果你的对象有垃圾引用你已经从 last 调用
Dispose()
处理过的对象,你会尝试再次处理它们!你已经处理好了,所以我不会尝试在垃圾对象引用上调用
Dispose
. 但这并没有阻止一个微妙的错误蔓延 .当用户调用
Dispose()
时:句柄 CursorFileBitmapIconServiceHandle 被销毁 . 稍后当垃圾收集器运行时,它将尝试再次销毁相同的句柄 .解决这个问题的方法是告诉垃圾收集器它不需要打扰最终确定对象 - 它的资源已经被清理掉了,不再需要它了 . 您可以通过在
Dispose()
方法中调用GC.SuppressFinalize()
来执行此操作:现在用户已经调用
Dispose()
,我们有:释放了非托管资源
释放了托管资源
GC运行终结器没有意义 - 一切都在处理 .
我不能使用Finalize来清理非托管资源吗?
Object.Finalize的文档说:
但MSDN文档也说,对于IDisposable.Dispose:
那是哪个呢?哪一个是我清理非托管资源的地方?答案是:
你当然可以将你的非托管清理放在终结器中:
问题是你不知道什么时候垃圾收集器会来完成你的对象 . 您的未管理,不需要,未使用的本机资源将一直存在,直到垃圾收集器最终运行 . 然后它会调用你的终结者方法;清理非托管资源 . Object.Finalize 的文档指出了这一点:
这是使用
Dispose
清理非托管资源的优点;当清理非托管资源时,您将了解并控制 . 他们的毁灭是"deterministic" .回答你原来的问题:为什么不现在释放内存,而不是GC决定这样做?我有一个面部识别软件,需要摆脱530 MB的内部图像 now ,因为他们're no longer needed. When we don' t:机器研磨到交换停止 .
奖金阅读
对于任何喜欢这种答案风格的人(解释原因,以及如何变得明显),我建议你阅读Don Box的Essential COM第一章:
直接链接:Chapter 1 sample by Pearson Publishing
磁铁:84bf0b960936d677190a2be355858e80ef7542c0
在35页中,他解释了使用二进制对象的问题,并在您眼前发明了COM . 一旦你意识到COM的原因,剩下的300页很明显,只是详细介绍了微软的实现 .
我认为每个曾经处理过对象或COM的程序员至少应该阅读第一章 . 对任何事情都是最好的解释 .
额外奖金阅读
Eric Lippert撰写When everything you know is wrong
在调用Dispose之后,不应再调用对象的方法(尽管对象应该容忍对Dispose的进一步调用) . 因此问题中的例子很愚蠢 . 如果调用Dispose,则可以丢弃对象本身 . 因此,用户应该放弃对该整个对象的所有引用(将它们设置为null),并且内部的所有相关对象将自动清除 .
至于关于托管/非托管的一般问题以及其他答案中的讨论,我认为对这个问题的任何答案都必须从非托管资源的定义开始 .
它归结为有一个函数,你可以调用将系统置于一个状态,还有一个函数,你可以调用它来使它恢复到该状态 . 现在,在典型示例中,第一个可能是返回文件句柄的函数,第二个可能是对
CloseHandle
的调用 .但是 - 这是关键 - 它们可以是任何匹配的功能对 . 一个 Build 一个国家,另一个撕毁它 . 如果状态已经构建但尚未拆除,则为实例资源存在 . 您必须安排在正确的时间进行拆解 - 资源不由CLR管理 . 唯一自动管理的资源类型是内存 . 有两种:GC和堆栈 . 值类型由堆栈管理(或通过在引用类型内搭接),引用类型由GC管理 .
这些函数可能导致状态更改,可以自由交错,或者可能需要完美嵌套 . 状态更改可能是线程安全的,也可能不是 .
看看Justice的问题中的例子 . 对日志文件缩进的更改必须完全嵌套,否则一切都会出错 . 它们也不太可能是线程安全的 .
可以与垃圾收集器搭便车,以清理您的非托管资源 . 但是,只有状态更改函数是线程安全的,并且两个状态的生命周期才能以任何方式重叠 . 因此,Justice的资源示例必须没有终结器!这对任何人都没有帮助 .
对于这些类型的资源,您可以在没有终结器的情况下实现
IDisposable
. 终结者绝对是可选的 - 它必须是 . 这在许多书中被掩盖或甚至没有提及 .然后,您必须使用
using
语句才能确保调用Dispose
. 这基本上就像搭便车(因为终结器是GC,using
是堆栈) .缺少的部分是你必须手动编写Dispose并调用你的字段和基类 . C / CLI程序员不必这样做 . 在大多数情况下,编译器会为它们编写它 .
有一个替代方案,我更喜欢完全嵌套并且不是线程安全的状态(除了其他任何东西,避免使用IDisposable可以避免与无法为每个实现IDisposable的类添加终结器的人争论的问题) .
你不是写一个类,而是编写一个函数 . 该函数接受一个委托来回调:
然后一个简单的例子是:
传入的lambda用作代码块,因此就像你使自己的控制结构与
using
具有相同的目的,除了你不再有调用者滥用它的任何危险 . 他们无法无法清理资源 .如果资源是可能具有重叠生命周期的那种,那么这种技术就不那么有用了,因为那时你想要能够构建资源A,然后是资源B,然后杀死资源A然后再杀掉资源B.你可以强制使用用户完全像这样嵌套 . 但是你需要使用
IDisposable
(但仍然没有终结器,除非你已经实现了线程安全,这不是免费的) .在您发布的示例中,它仍然不是"free the memory now" . 所有内存都是垃圾回收,但它可能允许在早期的_265679中收集内存 . 你必须运行一些测试才能确定 .
框架设计指南是指南,而不是规则 . 它们告诉您接口主要用于什么,何时使用它,如何使用它以及何时不使用它 .
我曾经使用IDisposable读取了一个简单的RollBack()故障代码 . 下面的MiniTx类将检查Dispose()上的标志,如果
Commit
调用从未发生过,那么它将自动调用Rollback
. 它增加了一层间接,使调用代码更容易理解和维护 . 结果看起来像:我也看到计时/日志代码做同样的事情 . 在这种情况下,Dispose()方法停止计时器并记录该块已退出 .
所以这里有一些具体的例子,它们不进行任何非托管资源清理,但是成功使用IDisposable来创建更干净的代码 .
如果有的话,我希望代码的效率低于离开时的效率 .
调用Clear()方法是不必要的,如果Dispose没有这样做,GC可能不会这样做......
IDisposable
通常用于利用using
语句,并利用一种简单的方法对托管对象进行确定性清理 .处理管理资源的最合理用例是为GC准备回收原本无法收集的资源 .
一个主要的例子是循环引用 .
虽然最佳做法是使用避免循环引用的模式,但如果你最终得到(例如)一个'child'对象,该对象的引用又回到了'parent',那么如果你放弃它就可以停止对父进程的GC收集参考并依赖GC - 如果你已经实现了终结器,它将永远不会被调用 .
解决此问题的唯一方法是通过在子项上将Parent引用设置为null来手动中断循环引用 .
在父级和子级上实现IDisposable是执行此操作的最佳方式 . 在父级上调用Dispose时,在所有子级和子级中调用DisposeDispose方法,将Parent引用设置为null .
方案我使用IDisposable:清理非托管资源,取消订阅事件,关闭连接
我用来实现IDisposable(不是线程安全)的成语:
如果你想 delete right now ,请使用 unmanaged memory .
看到:
Marshal.AllocHGlobal
Marshal.FreeHGlobal
Marshal.DestroyStructure
除了主要用作控制 system resources 的方法之外(完全由Ian的惊人答案覆盖,kudos!), IDisposable/using combo也可以用于 scope the state change of (critical) global resources :控制台,线程,进程,任何全局对象像一个应用程序实例 .
我写了一篇关于这种模式的文章:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/
它说明了如何以 reusable 和 readable 方式保护一些常用的全局状态:控制台颜色,当前线程文化,Excel应用程序对象属性......
您给出的代码示例不是
IDisposable
用法的好例子 . 字典清除通常不应该转到Dispose
方法 . 当字典项超出范围时,它将被清除并处理 . 需要IDisposable
实现释放一些即使在超出范围之后也不会释放/释放的内存/处理程序 .以下示例显示了IDisposable模式的一个很好的示例,其中包含一些代码和注释 .
大多数“非托管资源”讨论的一个问题是它们并没有真正定义该术语,但似乎暗示它与非托管代码有关 . 虽然许多类型的非托管资源确实与非托管代码进行交互,但在这些术语中考虑非托管资源是没有用的 .
相反,人们应该认识到所有管理资源的共同点:它们都需要一个对象,要求某些外部“事物”代表它做某事,损害其他一些“事物”,而另一个实体同意这样做直到另行通知 . 如果对象被抛弃并消失得无影无踪,那么任何东西都不会告诉外面的“事物”它不再需要代表不再存在的对象来改变它的行为;因此,“事物的有用性将永久地减少 .
因此,一个非托管资源代表一些外部“事物”的协议,以代表一个对象改变它的行为,如果该对象被放弃并且不再存在,那将无用地损害该外部“事物”的有用性 . 托管资源是这样一个协议的受益人,但是如果它被放弃则已经注册接收通知,并且在被销毁之前将使用这样的通知将其事务整理好 .
我看到很多答案已转移到谈论对托管和非托管资源使用IDisposable . 我建议将这篇文章作为我发现如何实际使用IDisposable的最佳解释之一 .
https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About
对于实际问题;如果你使用IDisposable来清理占用大量内存的托管对象,那么简短的答案就是 no . 原因是一旦你处理了IDisposable,你应该让它超出范围 . 此时,任何引用的子对象也超出范围并将被收集 .
唯一真正的例外是,如果您在托管对象中占用了大量内存,并且您已阻止该线程等待某些操作完成 . 如果在完成调用之后不需要的那些对象然后将这些引用设置为null可能允许垃圾收集器更快地收集它们 . 但是这种情况会代表需要重构的坏代码 - 而不是IDisposable的用例 .
是的,那段代码是完全冗余和不必要的,否则它就不会这样做(一旦MyCollection的实例超出范围,那就是 . )尤其是
.Clear()
调用 .回答你的编辑:排序 . 如果我这样做:
出于内存管理的目的,它在功能上与此相同:
如果你真的真的需要在瞬间释放内存,请致电
GC.Collect()
. 那里需要_265671 .首先是定义 . 对我来说,非托管资源意味着一些类,它实现了IDisposable接口或使用dll调用创建的东西 . GC不知道如何处理这些对象 . 如果类仅具有值类型,那么我不认为此类是具有非托管资源的类 . 对于我的代码,我遵循以下做法:
如果由我创建类使用一些非托管资源,那么这意味着我还应该实现IDisposable接口以清理内存 .
完成使用后立即清理对象 .
在我的dispose方法中,我遍历所有类的IDisposable成员并调用Dispose .
在我的Dispose方法中调用GC.SuppressFinalize(this)以通知垃圾收集器我的对象已被清理 . 我这样做是因为调用GC是昂贵的操作 .
作为额外的预防措施,我尝试多次调用Dispose() .
有时我添加私有成员_disposed并检查方法调用对象是否被清理 . 如果它被清理干净,那么生成ObjectDisposedException
以下模板演示了我在单词中描述的代码示例:
Dispose模式的目的是提供一种清理托管和非托管资源的机制,以及何时发生这种情况取决于如何调用Dispose方法 . 在您的示例中,Dispose的使用实际上并未执行与dispose相关的任何操作,因为清除列表对正在处置的集合没有影响 . 同样,将变量设置为null的调用也不会对GC产生影响 .
你可以看一下这个article,了解如何实现Dispose模式的更多细节,但它基本上是这样的:
这里最重要的方法是Dispose(bool),它实际上在两种不同的情况下运行:
disposing == true:该方法由用户代码直接或间接调用 . 可以处理托管和非托管资源 .
disposing == false:运行时已从终结器内部调用该方法,您不应引用其他对象 . 只能处理非托管资源 .
简单地让GC负责清理的问题是你无法真正控制GC何时运行一个收集周期(你可以调用GC.Collect(),但你真的不应该这样做)因此资源可能会停留比需要的时间更长 . 请记住,调用Dispose()实际上不会导致收集周期或以任何方式导致GC收集/释放对象;它只是提供了更加确定地清理所用资源的方法,并告诉GC已经执行了这次清理 .
IDisposable和处理模式的重点不在于立即释放内存 . 调用Dispose实际上甚至有可能立即释放内存的唯一一次是它处理disposing == false场景并操纵非托管资源 . 对于托管代码,内存实际上不会被回收,直到GC运行一个收集周期,你实际上无法控制(除了调用GC.Collect(),我已经提到过这不是一个好主意) .
由于.NET中的字符串不使用任何未管理的资源并且未实现IDisposable,因此您的方案无法真正有效,因此无法强制它们“清理” .