问题 检测“泄露”的IDisposable对象


有很多问题要求如何检测IDisposable对象泄漏。似乎答案是 “你不能”

我只是检查了最简单的测试用例,FxCop 10.0没有这样做,带有MSVS2010的ReSharper 4没有这样做。

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

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

WinDBG + SOS的魔术技巧怎么样?

即使没有现成的工具,我也想知道这在理论上是否可行(我的C#不是很尖锐)。

想法?

注意 这个问题的标题可能会产生误导。这里真正的问题应该是 是否一个 IDisposable 对象已经 Disposed() 正确。由于我认为这是一个错误,因此由GC处理并不重要。

编辑:解决方案:.NET Memory Profiler完成这项工作。我们只需要垃圾邮件几个 GC.Collect() 在程序结束时,我们的探查器可以正确地获取统计数据。


4747
2018-01-19 15:25


起源

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


答案:


你搜索不够努力。有很多.NET内存配置文件可以在程序运行时查看您的程序,并让您知道内存的使用位置(以及泄漏的内容)。

我会检查以下任何一项:

微软的CLR Memory Profiler(免费) 
RedGate ANTS Memory Profiler 
JetBrain的DotTrace(包括代码分析器) 
SciTech .NET内存分析器

更新

SciTech的.NET内存分析器具有一个名为“Dispose Tracker”的功能,该功能符合OP的要求,即仅在其应用程序中跟踪Dispose调用。


12
2018-01-19 15:27



那检测到了吗? Dispose 还是记忆?如果我的 IDisposable 对象就是这样 Console.WriteLine("baz"); (可怜的例子,我知道,但是你明白了)我想确保它实际上是由GC调用的吗? - kizzx2
@ kizzx2 - 它将检测所有内容,但是从那里你可以缩小它以找到你想要的东西。 - Justin Niessner
我对RedGate的ANTS Memory Profiler有过最好的体验。 - Uwe Keim
@Uwe Keim:RedGate的ANTS Memory Profiler会跟踪实际问的问题吗? (这是列表中唯一一个需要发送电子邮件进行评估的人 - 现在懒得这样做) - kizzx2
.NET Memory Profiler恰好具有名为“Dispose Tracker”的功能,它完全符合我的要求。不幸的是,现在这个答案对任何人都没有用,因为它只是列出了谷歌的搜索结果(并不是没有用的 - 它迫使我下去并尝试每一个来证明至少有一个是相关的)。你能编辑它至少包括关于.NET Memory Profiler的“Dispose Tracker”,这至少可以提供信息吗? - kizzx2


你可以通过向IDisposable对象添加Finalizer来实现。 在终结器中,您可以检查对象是否已被丢弃。如果尚未处理,您可以断言,或者将某些内容写入日志或其他内容。

 ~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 - 例如,可以用作模板来实现 一次性 例如,模式。

像这样,例如:

    /// <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);
        }
    }

3
2018-01-19 15:47



我不认为这是一种糟糕的做法。事实上,我认为这是一个 远 优于使用分析器来实现这么简单的方法。唯一的缺点是它无法检查其他人的类,因此仍然需要一个分析器。但是,当你可以控制添加a时,为什么不选择更简单的方法 Debug.Assert 在你的终结者?你是否真的喜欢在探查器中运行程序只是为了捕捉一些粗心的错误?我想知道什么样的软件开发可以使这种琐碎的方法“相当混乱”太多了:P - kizzx2
终结者引入了绩效惩罚,鉴于问题的隐含目标,这可能是不可接受的。 Jeffrey Richter的CLR通过C#第2版(p.477)指出,具有终结器的对象需要更长的时间来分配,升级到老一代(因此它们将在以后收集),并且在收集时需要额外的处理。 - Neil
@Neil Whitaker是对的。看到 框架设计指南第二版。 第329-330页。 “避免使类型最终化。”引用Joe Duffy的话说,“在负载很重的服务器上,你可能会发现一个处理器花费100%的时间来运行终结器。” - TrueWill
@ kizzx2 - 终结者是 不 必然。大多数IDisisable类都不需要终结器。对于其他许多人来说,SafeHandle后代就足够了。阅读Microsoft关于此主题的指南。 - TrueWill
@ kizzx2 - 但示例代码显示了 #if DEBUG  内 终结者,答案建议使用“作为模板来实现一次性模式”。 - TrueWill


虽然@Justin Niessner的推荐有效,但我发现使用完整的剖析器太重了。

我创建了我的家酿解决方案: EyeDisposable。它可以检测组件以检测何时 Dispose 没有被称为。


1
2018-05-22 12:16



谢谢,看起来很酷!我会爱的 静态的 尽管如此分析(也许作为R#插件)。一个小评论,在 克隆 自述文件的一部分必须运行 git submodule init 之前 git submodule update。 - Ohad Schneider
@OhadSchneider感谢提醒 - 静态分析会很酷但是对于非平凡的案例肯定有很多误报 - 而且老实说它比原来的范围要复杂得多 小 效用:P - kizzx2