首页 文章

IDisposable实现 - 'if (disposing)'应该怎么做

提问于
浏览
14

我一直在修复winforms应用程序中的一些内存泄漏问题,并注意到一些未明确部署的一次性对象(开发人员没有帮助,因为它没有进入 if (disposing) 子句 . 所有静态事件取消注册和收集清除已经放入 if (disposing) clause . 如果对象是一次性的,最好的做法就是调用Dispose,但不幸的是,有时会发生这种情况

如果有非托管对象,静态事件处理程序和某些托管集合需要在处理时清除 . 什么是决定应该进入的方式以及 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) 部分 .

以下是我的问题 .

  • 如果我有静态事件处理程序导致内存泄漏我应该在哪里取消注册?进出 if (disposing) 条款?

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

  • 如果我正在使用第三方一次性对象(例如:devExpress winform控件),我不确定它们是托管对象还是非托管对象 . 让's say I want to dispose them when disposing a form. How can I know what are managed and what are non-managed objects? Being disposable doesn' t说出来?在这种情况下,如何决定应该进入什么以及 if (disposing) 条款的内容?

  • 如果我不确定管理或解除管理的事情,那么从 if (disposing) 条款中处理/清除/取消注册事件可能会产生什么不良后果?让我们说它在处置之前检查null?

Edit

我的意思是事件取消注册就像下面这样 . Publisher是一个长期存在的实例,下面的行位于订阅者的构造函数中 . 在这种情况下,订户需要取消注册事件并在发布者之前进行处置 .

publisher.DoSomeEvent += subscriber.DoSomething;

6 回答

  • 3

    听起来你主要有托管对象,即实现IDisposable的对象 . 非托管代码将是IntPtr或句柄之类的东西,通常意味着调用非托管代码或P / Invoke来访问它们 .

    • 正如马赫普指出的那样,Dispose不是为了这个 . 当一个对象完成接收事件时,它应该注销它自己 . 如果这不可能,请考虑使用WeakReferences .

    • 这可能不应该处置,除非这些集合包含需要处置的对象 . 如果它们是一次性物品,那么它应该进入“if disposing”块并且您应该对集合中的每个项目进行处理 .

    • 如果它实现了IDisposable,它就被管理了

    • 在终结器调用时,不应访问其他托管代码对象,这是“if(disposing)”块之外的意思 .

  • 0

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

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

    第二种情况是协助处理非托管代码 . 实际上这与操作系统的C / C API调用有关,在这种情况下,您负责确保代码不是资源 .

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

    希望有点帮助 .

  • 0

    从广义上讲,托管资源在 if (disposing) 内部以及非托管资源之外 . 处置模式如下:

    • if (disposed) {

    如果此物体已经丢弃,请勿再次丢弃 .

    • if (disposing) {

    如果以编程方式请求处理( true ),则处置此对象拥有的受管资源(IDisposable对象) .

    如果处理是由垃圾收集器( false )引起的,请不要丢弃托管资源,因为垃圾收集器可能已经处理了所拥有的托管资源,并且会在应用程序终止之前对其进行定义处理 .

    • }

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

    • disposed = true

    将此物体标记为防止重复处理 . 重复处理可能会在步骤2或3导致NullReferenceException .

    Question 1
    根本不要将它们丢弃在 Dispose 方法中 . 如果您处理了该类的多个实例会发生什么?你每次处理静态成员,尽管它们已被处理掉 . 我找到的解决方案是处理AppDomain.DomainUnloaded事件并在那里执行静态处理 .

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

    Question 3
    IDisposable是一个托管接口 . 如果一个类实现了IDisposable,那么它就是一个托管类 . 在 if (disposing) 中处理托管对象 . 如果它没有实现IDisposable,它既可以管理也可以不需要处理,或者不受管理,应该在 if (disposing) 之外处理 .

    Question 4
    如果应用程序意外终止,或者不处理父级,则父级将第二次处理该子级 . 大多数托管对象可以安全地多次处理,但前提是它们已经正确构建 . 如果对象被多次丢弃,您可能会冒险(但不太可能)导致gargabe集合失败 .

  • 0

    If I have static event handlers that causes memory leaks where should I un-register them? In or out of if (disposing) clause?

    在实例上调用Dispose方法,其中在类级别使用静态事件处理程序 . 所以你不应该在处理中取消注册 . 通常,静态事件处理程序应该在类卸载时取消注册,或者在应用程序执行期间的某个时刻导致不再需要此事件处理程序 .

    For all manages and un-managed resources better implement IDisposable pattern. 见这里http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspxFinalize/Dispose pattern in C#

  • 0

    除非类的唯一目的是封装一些资源(*),如果放弃需要清理它,它不应该有一个终结器,并且Dispose(bool)永远不应该被调用值为False,而是调用Dispose (假)应该没有效果 . 如果一个继承的类需要保存需要清理的资源(如果放弃),它应该将该资源封装到专门用于该目的的对象中 . 这样,如果主对象被放弃,并且没有其他人持有对封装资源的对象的任何引用,则该对象可以执行其清理,而不必使主对象保持活动以进行额外的GC循环 .

    顺便说一句,我不喜欢微软处理Disposed旗帜 . 我建议非虚拟Dispose方法应该使用Interlocked.Exchange的整数标志,以确保从多个线程调用Dispose只会导致一次执行dispose逻辑 . 标志本身可能是私有的,但应该有一个受保护和/或公共的Disposed属性,以避免要求每个派生类实现自己的处理标志 .

    (*)资源不是某种特定类型的实体,而是一个松散的术语,包含一个类可能要求某些外部实体代表它执行的任何事情,而外部实体需要被告知停止这样做 . 最典型的情况是,外部实体将授予该类独占使用的某些东西(无论是内存区域,锁定,GDI句柄,文件,套接字,USB设备等),但在某些情况下外部实体可能已经要求实体肯定地做某事(例如,每次发生事件时运行事件处理程序)或保留某些东西(例如,线程静态对象引用) . “非托管”资源是放弃后不会被清理的资源 .

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

  • 2

    什么应该进入'如果(处置)'

    All Managed objects 应该进入if(disposing)子句 . 托管对象不应该在它旁边(它将通过最终化执行) .

    原因是如果该类具有析构函数,则垃圾收集器完成过程可以执行Dispose(false) . 通常只有在存在非托管资源时才会有析构函数 . 垃圾收集器的终结没有特定的顺序来执行Finalize方法 . 因此,在最终确定发生时,其他托管对象可能不在内存中 .

相关问题