2011-10-04 17 views
13

我一直在修复winforms应用程序中的一些内存泄漏问题,并注意到一些不显式放弃的对象(开发人员没有称为Dispose方法)。 Finalize方法的实现也没有帮助,因为它不在if (disposing)子句中。所有的静态事件注销和收集清除已放入if (disposing)条款。最好的做法是调用的Dispose如果对象是一次性的,但不幸的是如果有非托管对象,静态的事件处理程序和需要处置时,清除一些管理的集合这种情况有时一次性实现 - 应该如何处理'if(disposing)'

。有什么方法可以决定什么应该进入以及应该从if (disposing)条款中排除。

Dispose method.

// 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)部分。

以下是我的问题。

  1. 如果我有导致内存泄漏的静态事件处理程序,我应该在哪里取消注册它们?有没有if (disposing)条款?

  2. 如果我有一些导致内存泄漏的集合,我应该在哪里清除它们?有没有if (disposing)条款?

  3. 如果我使用第三方的一次性对象(例如:devExpress winform控件),我不确定它们是托管对象还是非托管对象。假设我想在处理表单时处理它们。我怎么知道什么是管理的,什么是非管理对象?一次性使用不是这样说的吗?在这种情况下,如何确定应该进入哪些内容以及从if (disposing)条款中应该列出哪些内容?

  4. 如果我不确定某件事情是否管理或不受管理,那么if (disposing)子句中处理/清除/取消注册事件的不良后果是什么?假设它在处置之前检查为空?

编辑

我的意思是事件未登记是一样的东西下面。 Publisher是一个长期存在的实例,下面一行是订阅者的构造函数。在这种情况下,用户需要取消注册该事件并在发布者之前进行处置。

publisher.DoSomeEvent += subscriber.DoSomething; 

回答

0

我应该去 '如果(处置)'

所有管理对象应该里面去,如果(处置)条款。被管理的对象不应该放在它的外面(这将通过最终确定来执行)。

原因是垃圾收集器定稿过程可以执行Dispose(false)如果该类具有析构函数。通常情况下,只有存在非托管资源时才有一个析构函数。垃圾收集器的最终化并没有执行Finalize方法的特定顺序。所以,其他管理对象可能不会在发生时间内存在于内存中。

0

如果我有静态的事件处理程序,导致内存泄漏我应该在哪里注销呢? (处置)条款中是否出现?

Dispose方法被称为上,其中为静态事件处理程序是在类层次使用实例。所以你不应该取消注册它们。通常是静态的事件处理程序应取消其注册时的类卸载或应用程序的执行过程中某些时候,你得到这个事件处理程序没有更多的要求。

对于所有管理和未管理的资源更好地实现IDisposable模式。 看到这里 http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspxFinalize/Dispose pattern in C#

+0

实例订阅静态事件并不罕见。这些实例应该在if(Disposing)子句中取消订阅。 – supercat

2

广义上说,管理资源配置中if (disposing)和非托管资源在它之外。该Dispose模式的工作原理是这样的:

  1. if (disposed) {

    如果此对象已经设置,不处置它第二次。

  2. if (disposing) {

    如果编程(true)被要求处置,处置该对象拥有管理资源(IDisposable的对象)。

    如果处置是由垃圾收集器(false),因为垃圾收集器可能已经丢弃了所有的管理资源,请不要将其管理的资源,并会definitelty处理他们的应用程序终止前引起的。

  3. }

    处置非托管资源,并释放所有对它们的引用。步骤1确保只发生一次。

  4. disposed = true

    标示为设置以防止重复处理该对象。重复处理可以在步骤2或3

问题1
不要在Dispose方法处置它们都导致一个NullReferenceException。如果您处理了该课程的多个实例,会发生什么?尽管已经处置了静态成员,但每次都会处理静态成员。我找到的解决方案是处理AppDomain.DomainUnloaded事件,并在那里执行静态处置。

问题2
这一切都取决于集合的项目是托管还是非托管。可能值得创建托管包装,为您正在使用的任何非托管类实现IDisposable,以确保管理所有对象。

问题3
IDisposable是一个托管界面。如果一个类实现了IDisposable,它是一个托管类。在if (disposing)内部处理管理对象。如果它没有实现IDisposable,则它可以被管理并且不需要处理,或者不被管理,并且应该被放置在if (disposing)之外。

问题4
如果应用程序意外终止或不使用人工处理,垃圾收集处置按随机顺序的所有对象。子对象可以在其父母被处置之前被处置,导致父母第二次处置该子女。大多数托管对象可以安全地多次处理,但前提是它们已经正确构建。如果一个对象被多次处置,你会冒(尽管不太可能)导致gargabe集合失败。

+0

垃圾回收器永远不会调用disposer。永远。 C#有一个析构函数(a.k.a和一个.Net终结器),当且仅当该对象被垃圾收集时才会被调用。 – Spence

+0

对象的析构函数的实现是可选的。 – Spence

+0

@Spence,你是对的。 'Finalize'方法(我使用VB)是可选的,但如果您有非托管资源需要处理,则必须使用该方法。它被用作垃圾收集器入口点到'Dispose(Boolean)'方法。 –

0

听起来你主要有管理对象,即实现IDisposable的对象。非托管代码可能是IntPtr或句柄,这通常意味着调用非托管代码或P/Invoke来获取它们。

  1. 正如Maheep所指出的那样,Dispose并不是为此而设计的。当一个对象完成接收事件时,它应该注销自己。如果这不可能,请考虑使用WeakReferences。

  2. 这可能不应该在处置,除非这些集合包含需要处理的对象。如果它们是一次性对象,那么它应该放在“如果处置”块中,并且应该在集合中的每个项目上调用处置。

  3. 如果实现IDisposable它的管理

  4. 当这正是作为“如果(处置)”块手段之外终结叫你不应该访问其他托管代码的对象。

3

这里要记住的关键是IDisposable的目的。它的工作是确定性地帮助你你的代码持有的发布资源,之前对象被垃圾收集。这实际上是C#语言团队选择关键字using的原因,因为括号决定了应用程序所需的对象及其资源的范围。

例如,如果打开与数据库的连接,则需要释放该连接并在完成处理后尽快关闭它,而不是等待下一次垃圾回收。这是您实施处理器的地点和原因。

第二种情况是协助非托管的代码。实际上,这与C++/C API调用操作系统有关,在这种情况下,您有责任确保代码不会泄露。尽管许多.Net被写入简单的P/Invoke到现有的Win32 API,但这种情况很常见。从操作系统(例如Mutex)封装资源的任何对象都将实现Disposer,以便您安全并确定地释放其资源。

但是,这些API将实施析构函数,以保证,如果你不正确使用资源,它不会被操作系统泄漏。当你的终结器被调用时,你不知道你引用的其他对象是否已经被垃圾收集,这就是为什么对它们进行函数调用是不安全的(因为它们可能抛出NullReferenceException),只有非托管引用根据定义不能垃圾收集)将提供给终结者。

希望能有所帮助。

+0

干杯。更新了我的答案。 – Spence

0

除非一个类的唯一目的是封装某些资源(*),如果放弃它,则需要清理它,它不应该有一个终结器,并且不应该使用值为False的方式调用Dispose(bool) ,但调用Dispose(False)应该没有效果。如果一个继承类需要持有一个需要清理的资源(如果被放弃),它应该将该资源封装到专门用于该目的的对象中。这样,如果主对象被抛弃,并且没有其他人持有对封装资源的对象的引用,那么该对象可以执行清理,而无需保持主对象在额外的GC循环中保持活动状态。

顺便提一句,我不喜欢微软处理Disposed标志。我建议非虚拟Dispose方法应该在Interlocked.Exchange中使用整数标志,以确保从多个线程中调用Dispose将仅导致一次执行处理逻辑。该标志本身可能是私有的,但应该有一个受保护的和/或公共的Disposed属性,以避免要求每个派生类实现自己的处置标志。 (*)资源不是某种特定类型的实体,而是一个松散的术语,它包含任何类别可能要求某个外部实体代表其执行的任何事情,该外部实体需要被告知停止这样做。最典型的是,外部实体将授予该类独占使用的东西(无论是内存区域,锁,GDI句柄,文件,套接字,USB设备等),但在某些情况下,外部实体可能被要求肯定地做某事(例如,每次发生事件时运行一个事件处理程序)或持有某些东西(例如线程静态对象引用)。 “非托管”资源是一种如果放弃不会被清理的资源。

顺便说一下,请注意,虽然微软可能已经打算封装非托管资源的对象应该清理它们,如果放弃,有一些类型的资源真的不切实际。例如,考虑将对象引用存储在线程静态字段中的对象,并在Dispose'd时自动清空该对象引用(处置将自然必须发生在创建对象的线程上)。如果对象被放弃但线程仍然存在(例如在线程池中),那么线程静态引用的目标可以很容易地无限期地保持活动状态。即使没有任何对被抛弃对象的引用使其Finalize()方法运行,被弃用对象也很难找到并销毁位于某个线程中的线程静态引用。

相关问题