我一直在修复winforms应用程序中的一些内存泄漏问题,并注意到一些非显式Disposed的一次性对象(开发人员没有调用Dispose方法)。 Finalize方法的实现也没有帮助,因为它没有进入if (disposing)
子句。所有静态事件取消注册和集合清除都已放入if (disposing)
子句中。如果对象是一次性的,最好的做法就是调用Dispose,但不幸的是,有时会发生这种情况
如果存在非托管对象,静态事件处理程序和一些托管时需要清除的托管集合。什么是决定应该进入什么以及应该从if (disposing)
条款中找出什么的方法。
// 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)
{
if (!disposed)
{
if (disposing)
{
// Free other state (managed objects).
}
// Free your own state (unmanaged objects).
// Set large fields to null.
disposed = true;
}
}
It says托管对象应该在if (disposing)
中,只有在开发人员显式调用Dispose方法时才能正常执行。如果已经实现了Finalize方法并且开发人员忘记调用Dispose方法,那么通过Finalizer执行的执行不会进入if (disposing)
部分。
以下是我的问题。
如果我有静态事件处理程序导致内存泄漏,我应该在哪里取消注册?进出if (disposing)
条款?
如果我有一些导致内存泄漏的集合,我应该在哪里清除它们?进出if (disposing)
条款?
如果我使用第三方一次性对象(例如:devExpress winform控件),我不确定它们是托管对象还是非托管对象。假设我想在处理表单时处理它们。我如何知道托管什么以及什么是非托管对象?一次性不说吗?在这种情况下,如何决定应该进入什么以及if (disposing)
条款应该包含哪些内容?
如果我不确定管理或解除管理的事情,那么从if (disposing)
条款中处理/清除/取消注册事件可能会产生什么不良后果?假设它在处置之前检查为空?
修改
我的意思是因为取消注册的事件如下所示。 Publisher是一个长期存在的实例,下面的行位于订阅者的构造函数中。在这种情况下,订户需要取消注册事件并在发布者之前进行处置。
publisher.DoSomeEvent += subscriber.DoSomething;
答案 0 :(得分:3)
这里要记住的关键是IDisposable
的目的。它的工作是帮助您确定性地释放代码所持有的资源,在对象被垃圾收集之前。这实际上是C#语言团队选择关键字using
的原因,因为括号确定了应用程序所需的对象及其资源的范围。
例如,如果打开与数据库的连接,则需要释放该连接并在完成连接后尽快关闭它,而不是等待下一次垃圾回收。这就是您实施Disposer的地点和原因。
第二种方案是协助非托管代码。实际上这与操作系统的C ++ / C API调用有关,在这种情况下,您有责任确保代码不会泄露。尽管.Net被编写为简单的P / Invoke到现有的Win32 API,但这种情况很常见。封装来自操作系统的资源(例如Mutex)的任何对象都将实现Disposer,以允许您安全且确定地释放它的资源。
但是,这些API 也实现析构函数,以保证如果您没有正确使用资源,它将不会被操作系统泄露。当你的终结器被调用时,你不知道你引用的其他对象是否已被垃圾收集,这就是为什么对它们进行函数调用是不安全的(因为它们可以抛出NullReferenceException
),只是非定期引用(根据定义,不能进行垃圾回收)将可用于终结器。
希望有所帮助。
答案 1 :(得分:2)
从广义上讲,托管资源位于if (disposing)
内部,非托管资源位于其中。处置模式如下:
if (disposed) {
如果此物品已经处理掉,请勿再次丢弃。
if (disposing) {
如果以编程方式请求处置(true
),则处置此对象拥有的托管资源(IDisposable对象)。
如果处理是由垃圾收集器(false
)引起的,请不要丢弃托管资源,因为垃圾收集器可能已经处理了所拥有的托管资源,并且会在应用程序终止之前对其进行定义处理。
}
处置非托管资源并释放对它们的所有引用。第1步确保只发生一次。
disposed = true
将此物体标记为防止重复处理。重复处理可能会在步骤2或3处导致NullReferenceException。
问题1
根本不要在Dispose
方法中处理它们。如果您处理了该类的多个实例会发生什么?你每次都要处理静态成员,尽管它们已被处理掉了。我找到的解决方案是处理AppDomain.DomainUnloaded事件并在那里执行静态处理。
问题2
这一切都取决于集合的项目是管理还是非管理。可能值得创建托管包装器,为您正在使用的任何非托管类实现IDisposable,确保管理所有对象。
问题3
IDisposable是一个托管接口。如果一个类实现了IDisposable,那么它就是一个托管类。在if (disposing)
内部署托管对象。如果它没有实现IDisposable,则它是受管理的,不需要处理,或者不受管理,应该在if (disposing)
之外部署。
问题4
如果应用程序意外终止,或者不使用手动处理,则垃圾收集器会以随机顺序处理所有对象。子对象可以在其父母被处置之前被处置,从而导致孩子被父母第二次处置。大多数托管对象可以安全地多次处理,但前提是它们已经正确构建。如果对象被多次丢弃,你冒险(但不太可能)导致gargabe集合失败。
答案 2 :(得分:0)
如果我有静态事件处理程序导致内存泄漏,我应该在哪里取消注册?进出if(处理)条款?
在实例上调用Dispose方法,其中在类级别使用静态事件处理程序。所以你不应该在处理中取消注册。通常,静态事件处理程序应该在类卸载时取消注册,或者在应用程序执行期间的某个时刻导致不再需要此事件处理程序。
对于所有管理和未管理的资源,更好地实施IDisposable模式。 看这里 http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx 和 Finalize/Dispose pattern in C#
答案 3 :(得分:0)
听起来你主要有托管对象,即实现IDisposable的对象。非托管代码可能是IntPtr或句柄,通常意味着调用非托管代码或P / Invoke来访问它们。
正如马赫普指出的那样,Dispose不是为了这个。当一个对象完成接收事件时,它应该注销它自己。如果那是不可能的,请考虑使用WeakReferences。
除非这些集合包含需要处理的对象,否则这可能不应该进行处置。如果它们是一次性物品,那么它应该进入“if disposing”区块,你应该对集合中的每个项目进行处理。
如果它实现了IDisposable,那就是托管
在终结器调用时,不应访问其他托管代码对象,这是“if(disposing)”块之外的意思。
答案 4 :(得分:0)
除非类的唯一目的是封装某些资源(*),如果放弃需要清理它,它不应该有一个终结器,并且永远不应该调用Dispose(bool),值为False,但是调用Dispose(False)应该没有效果。如果一个继承的类需要保存需要清理的资源(如果放弃),它应该将该资源封装到专门用于该目的的对象中。这样,如果主对象被放弃,并且没有其他人拥有对封装资源的对象的任何引用,那么该对象可以执行其清理,而无需使主对象保持活动以进行额外的GC循环。
顺便说一句,我不喜欢微软处理Disposed标志。我建议非虚拟Dispose方法应该使用Interlocked.Exchange的整数标志,以确保从多个线程调用Dispose只会导致一次执行dispose逻辑。标志本身可能是私有的,但应该有一个受保护和/或公共的Disposed属性,以避免要求每个派生类实现自己的处理标志。
(*)资源不是某种特定类型的实体,而是一个松散的术语,包含一个类可能要求某个外部实体代表它做的任何事情,需要告诉外部实体停止这样做。最典型的情况是,外部实体将授予该类独占使用的某些东西(无论是内存区域,锁定,GDI句柄,文件,套接字,USB设备等),但在某些情况下外部实体可能已经要求实体肯定地做某事(例如,每次发生事件时运行事件处理程序)或保留某些东西(例如,线程静态对象引用)。 “非托管”资源是指如果放弃将无法清理的资源。
顺便说一句,请注意,尽管微软可能已经打算将封装非托管资源的对象清理干净,但有些类型的资源实际上并不实用。例如,考虑一个在线程静态字段中存储对象引用的对象,并在Dispose'd时将该对象引用置空(当然,必须在创建对象的线程上进行处理)。如果对象被放弃但线程仍然存在(例如在线程池中),则线程静态引用的目标可以很容易地无限期地保持活动。即使没有对被放弃对象的任何引用,因此它的Finalize()方法运行,被放弃的对象也很难找到并销毁某个线程中的线程静态引用。答案 5 :(得分:0)
'if(disposing)'
应该包含哪些内容
所有托管对象应该进入if(disposing)子句。托管对象不应该在它的旁边(将通过最终化执行)。
原因是如果该类具有析构函数,则垃圾收集器完成过程可以执行Dispose(false)。通常只有在存在非托管资源时才会有析构函数。垃圾收集器的终结没有特定的顺序来执行Finalize方法。因此,在最终确定发生时,其他托管对象可能不在内存中。