【问题标题】:Detecting "leaked" IDisposable objects检测“泄露”的 IDisposable 对象
【发布时间】:2011-06-11 20:16:33
【问题描述】:

有很多问题询问如何检测 IDisposable 对象泄漏。答案似乎是"you can't"

我刚刚检查了最简单的测试用例,FxCop 10.0 不这样做,ReSharper 4 with MSVS2010 不这样做。

这对我来说似乎是错误的,比 C 中的内存泄漏更糟糕(至少我们已经建立了检测工具)。

我在想:是否有可能,使用反射和其他模糊的高级技术,我可以在运行时在终结器中注入检查以查看是否已调用 Dispose

WinDBG+SOS 的魔术怎么样?

即使没有现成的工具可以做到这一点,我想知道这在理论上是否可行(我的 C# 不是很敏锐)。

想法?

注意 此问题的标题可能具有误导性。这里真正的问题应该是IDisposable 对象是否正确地是Disposed()。被 GC 处理掉不算数,因为我认为这是一个错误。

编辑:解决方案:.NET Memory Profiler 完成这项工作。我们只需要在程序结束时向多个GC.Collect() 发送垃圾邮件,以使我们的分析器能够正确获取统计信息。

【问题讨论】:

  • 工具存在于 C++ 而可能不存在于 C# 的原因是 C# 中的资源根本不同,因为非托管资源不再与对象生存期耦合。在 C# 和 C++ 中,可以跟踪的是对象生命周期以及对象是否已被正确处理。但是 C# 中的一次性资源并没有以任何方式绑定到对象生命周期,这使得跟踪它们变得更加困难。作为比较,请尝试在 C++ 中跟踪未通过 RAII 绑定到对象生命周期的泄漏 GDI 资源。也不是那么容易。
  • 我一直在思考这个问题。我养成了在编写代码时快速检查类型以查看它们是否继承自 IDisposable 的习惯。如果他们这样做,我将它们包装在using 中,在它们需要生存的范围内。它对现有代码没有任何作用,但我只是想我会提到它。
  • 查看这篇文章,您可以在其中使用 Visual Studio 代码分析在编译时检测 iDisposable 问题:stackoverflow.com/a/6213977/2862

标签: c# .net idisposable memory-leaks


【解决方案1】:

你搜索的不够仔细。有很多 .NET 内存分析器可以在程序运行时查看它,并让您知道内存的使用位置/方式(以及泄漏的原因)。

我会检查以下任何一项:

Microsoft's CLR Memory Profiler (free)
RedGate ANTS Memory Profiler
JetBrain's DotTrace (includes code profiler as well)
SciTech .NET Memory Profiler

更新

SciTech 的 .NET Memory Profiler 有一个名为“Dispose Tracker”的功能,可以满足 OP 仅跟踪其应用程序中的 Dispose 调用的要求。

【讨论】:

  • 检测到Dispose 还是仅检测内存?如果我的 IDisposable 对象只有 Console.WriteLine("baz"); 怎么办(我知道,但你明白这一点)并且我想确保它实际上不是由 GC 调用的?
  • @kizzx2 - 它会检测所有内容,但您可以从那里缩小范围以找到您要查找的内容。
  • 我在 RedGate 的 ANTS Memory Profiler 上获得了最好的体验。
  • @Uwe Keim:RedGate 的 ANTS Memory Profiler 是否跟踪实际提出的问题? (这是列表中唯一需要发送电子邮件进行评估的邮件——现在懒得这么做)
  • .NET Memory Profiler 恰好有一个名为“Dispose Tracker”的功能,它完全符合我的要求。不幸的是,现在这个答案对任何人都不是真正有用的,因为它只是列出了谷歌搜索结果(并不是说它没有用——它迫使我去尝试每个人以证明至少有一个是相关的)。您可以对其进行编辑以至少包含有关 .NET Memory Profiler 的“Dispose Tracker”,这样至少可以提供信息吗?
【解决方案2】:

您可以通过向您的 IDisposable 对象添加终结器来做到这一点。 在终结器中,您可以检查对象是否已被释放。如果它没有被释放,你可以断言它,或者在日志中写一些东西,或者其他什么。

 ~Disposable()
 {
#if DEBUG
            // In debug-builds, make sure that a warning is displayed when the Disposable object hasn't been
            // disposed by the programmer.

            if( _disposed == false )
            {
                System.Diagnostics.Debug.Fail ("There is a disposable object which hasn't been disposed before the finalizer call: {0}".FormatString (this.GetType ().Name));
            }
#endif
            Dispose (false);
 }

您可以将此功能分解到基类 -Disposable- 中,例如,它可以用作模板来实现 Disposable 模式。

例如这样:

    /// <summary>
    /// Abstract base class for Disposable types.    
    /// </summary>
    /// <remarks>This class makes it easy to correctly implement the Disposable pattern, so if you have a class which should
    /// be IDisposable, you can inherit from this class and implement the DisposeManagedResources and the
    /// DisposeUnmanagedResources (if necessary).
    /// </remarks>
    public abstract class Disposable : IDisposable
    {
        private bool                    _disposed = false;

        /// <summary>
        /// Releases the managed and unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose (true);
            GC.SuppressFinalize (this);
        }

        /// <summary>
        /// Releases the unmanaged and managed resources.
        /// </summary>
        /// <param name="disposing">When disposing is true, the managed and unmanaged resources are
        /// released.
        /// When disposing is false, only the unmanaged resources are released.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
        protected void Dispose( bool disposing )
        {
            // We can suppress the CA1063 Message on this method, since we do not want that this method is 
            // virtual.  
            // Users of this class should override DisposeManagedResources and DisposeUnmanagedResources.
            // By doing so, the Disposable pattern is also implemented correctly.

            if( _disposed == false )
            {
                if( disposing )
                {
                    DisposeManagedResources ();
                }
                DisposeUnmanagedResources ();

                _disposed = true;
            }
        }

        /// <summary>
        /// Override this method and implement functionality to dispose the 
        /// managed resources.
        /// </summary>
        protected abstract void DisposeManagedResources();

        /// <summary>
        /// Override this method if you have to dispose Unmanaged resources.
        /// </summary>
        protected virtual void DisposeUnmanagedResources()
        {
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="Disposable"/> is reclaimed by garbage collection.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
        ~Disposable()
        {
#if DEBUG
            // In debug-builds, make sure that a warning is displayed when the Disposable object hasn't been
            // disposed by the programmer.

            if( _disposed == false )
            {
                System.Diagnostics.Debug.Fail ("There is a disposable object which hasn't been disposed before the finalizer call: {0}".FormatString (this.GetType ().Name));
            }
#endif
            Dispose (false);
        }
    }

【讨论】:

  • 我不认为这是一个糟糕的方法。事实上,我认为这是一种优于使用分析器实现如此简单的方法的 方法。唯一的缺点是它不能检查其他人的课程,所以仍然需要一个分析器。但是,当您可以控制在终结器中添加 Debug.Assert 时,为什么不选择更简单的方法呢?你真的喜欢一直在探查器中运行程序,只是为了发现一些粗心的错误吗?我想知道什么样的软件开发可以使这种微不足道的方法相比之下“过于混乱和复杂”:P
  • 终结者引入了性能惩罚,考虑到问题的隐含目标,这可能是不可接受的。 Jeffrey Richter 通过 C# 第 2 版的 CLR(第 477 页)指出,具有终结器的对象需要更长的时间来分配、提升到老一代(因此它们稍后被收集),并且需要在收集时进行额外处理。
  • @Neil Whitaker 是正确的。请参阅Framework Design Guidelines 2nd Ed. 第 329-330 页。 “避免使类型最终化。”引用 Joe Duffy 的话:“在负载很重的服务器上,您可能会发现一个处理器 100% 的时间都花在运行终结器上。”
  • @kizzx2 - 终结者并非不可避免。大多数 IDisposable 类不需要终结器。对于其余的许多人来说,SafeHandle 的后代就足够了。阅读 Microsoft 关于此主题的指南。
  • @kizzx2 - 但示例代码显示了#if DEBUG inside 终结器,答案建议使用此“作为实现 Disposable 模式的模板”。跨度>
【解决方案3】:

虽然@Justin Niessner 的推荐有效,但我发现使用完整的分析器过于繁重。

我创建了我的自制解决方案:EyeDisposable。它检测程序集以检测何时未调用 Dispose

【讨论】:

  • 谢谢,看起来很酷!不过,我会喜欢 static 分析(也许作为 R# 插件)。在自述文件的 Cloning 部分中的一个小注释必须在 git submodule update 之前运行 git submodule init
  • @OhadSchneider 感谢您的提醒——静态分析会很酷,但对于非平凡的案例肯定会有很多误报——老实说,它比原来的范围要复杂得多 小实用程序:P
猜你喜欢
  • 1970-01-01
  • 2014-02-25
  • 1970-01-01
  • 1970-01-01
  • 2014-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-23
相关资源
最近更新 更多