处置的重点 是 释放非托管资源。它需要在某个时候完成,否则它们将永远不会被清除。垃圾收集器不知道 如何
调用
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()给您,该怎么办?然后他们将泄漏一些 不受管理的 资源!
注意: 它们不会泄漏 托管
资源,因为最终垃圾收集器将在后台线程上运行,并释放与任何未使用的对象关联的内存。这将包括您的对象以及您使用的所有托管对象(例如Bitmap和DbConnection)。
如果对方忘了打电话
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 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的Essential COM的第一章:
- 直接链接:Pearson Publishing的第1章样本
- 磁铁:84bf0b960936d677190a2be355858e80ef7542c0
在35页中,他解释了使用二进制对象的问题,并在您眼前发明了COM。一旦了解了COM 的 原因
,剩下的300页就显而易见了,只详细介绍了Microsoft的实现。
我认为每个曾经处理过对象或COM的程序员都应该至少阅读第一章。这是有史以来最好的解释。
额外奖金阅读当您知道的一切都不对时,埃里克·利珀特(Eric Lippert)
因此,确实很难编写正确的终结器,而 我能给您的最佳建议是不要尝试 。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)