23

最近,我正在研究一些关于未处理对象的棘手错误。

我在代码中发现了一些模式。据报道,一些 m_foo 没有被释放,而 SomeClass 的所有实例似乎都被释放了。

public class SomeClass: IDisposable
{
    void Dispose()
    {
       if (m_foo != null)
       {
          m_foo.Dispose();
       }
       if (m_bar != null)
       {
          m_bar.Dispose();
       }   
    }

    private Foo m_foo;

    private Bar m_bar;

}

我怀疑 Foo.Dispose 可能会引发异常,因此不会执行以下代码,因此不会释放 m_bar。

由于 Foo/Bar 可能来自第三方,因此不能保证不抛出异常。

如果只是用 try-catch 包装所有 Dispose 调用,代码将变得笨拙。

处理这个问题的最佳做法是什么?

4

7 回答 7

35

确实,泄漏 dispose 方法的异常可能非常糟糕,特别是因为实现 IDisposable 的东西通常会指定一个调用 Dispose 的终结器。

问题在于,通过处理异常来彻底解决问题可能会给您留下一些非常难以调试的情况。如果您的 IDisposable 分配了一个仅在处置后才释放的关键部分,该怎么办。如果您忽略发生异常的事实,您可能最终会陷入死锁中央。我认为 Dispose 中的失败应该是您希望尽早失败的情况之一,因此您可以在发现错误后立即修复该错误。

当然,这完全取决于要处理的对象,对于某些对象您可能能够恢复,而其他对象则不能。作为一般经验法则,Dispose 在正确使用时不应该引发异常,并且您不应该围绕正在调用的嵌套 Dispose 方法中的异常进行防御性编码。

您真的不想在地毯下扫除 OutOfMemoryException 吗?

如果我有一个狡猾的 3rd 方组件,它在 Dispose 上任意抛出异常,我会修复它并将其托管在一个单独的进程中,当它开始播放时我可以将其拆除。

于 2009-06-23T03:39:00.073 回答
7

如果在终结上下文中调用 Dispose() 并引发异常,您的进程将被终止。

如果您怀疑 Foo.Dispose() 正在引发异常,我会尽可能最后处理它,并将其包装在 try/catch 中。尽你所能在 catch 中摆脱它 - 将引用设置为 null。从 Dispose() 抛出异常是非常糟糕的,应该避免。

不幸的是,如果这是有缺陷的第三方代码,最好的办法是让他们修复它。你不应该在它之后手动清理。

希望有帮助。

于 2009-06-23T03:18:30.283 回答
5

根据设计规则

“IDisposable.Dispose 方法不应引发异常。”

因此,如果您的程序由于 Dispose() 未处理的异常而崩溃 - 请参阅官方解决方案

于 2016-03-04T18:18:35.067 回答
2

因为您不必在 using() 语句中分配变量 - 为什么不为此使用“堆叠”使用语句?

void Dispose()
{
    // the example in the question didn't use the full protected Dispose(bool) pattern
    // but most code should have if (!disposed) { if (disposing) { ...

    using (m_foo)
    using (m_bar)  
    {
        // no work, using statements will check null 
        // and call Dispose() on each object
    }

    m_bar = null;
    m_foo = null;
}

'stacked' using 语句扩展如下:

using (m_foo)
{
    using (m_bar) { /* do nothing but call Dispose */ }
}

所以 Dispose() 调用被放在单独的 finally 块中:

try {
    try { // do nothing but call Dispose
    }
    finally { 
        if (m_bar != null)
            m_bar.Dispose(); 
    }
finally { 
    if (m_foo != null)
        m_foo.Dispose();
}

我很难在一个地方找到这个参考。'stacked' using 语句可在旧的 Joe Duffy 博客文章中找到(请参阅“C# 和 VB 使用语句,C++ 堆栈语义”部分)。IDisposable 上的许多 StackOverflow 答案都引用了 Joe Duffy 的帖子。我还发现了一个最近的问题,其中局部变量的堆叠 using 语句似乎很常见。除了C# 语言规范,我在任何地方都找不到 finally 块的链接(C# 3.0 规范中的第 8.13 节),并且仅适用于单个“使用”块中的多个变量,这并不是我所提议的,但是如果您反汇编 IL,您会发现 try/finally 块是嵌套的. 在空值检查中,同样来自 C# 规范:“如果获取了空资源,则不会调用 Dispose,也不会引发异常。”

于 2009-10-24T06:46:22.440 回答
1

Dispose 不应该抛出任何异常。如果是这样——写得不好,所以……</p>

try { some.Dispose(); } catch {}

应该足够了。

于 2010-10-20T07:18:53.433 回答
0

为了避免重复处理对象的代码,我编写了以下静态方法。

    public static void DisposeObject<T>(ref T objectToDispose) where T : class
    {
        IDisposable disposable = objectToDispose as IDisposable;
        if (disposable == null) return;

        disposable.Dispose();
        objectToDispose = null;
    }

要点是您可以将其设为函数,因此您只需为每个要处理的对象键入一行,从而保持 Dispose 方法简洁明了。在我们的例子中,将指针设置为空是惯例,因此是 ref 参数。

在您的情况下,您可能想要添加异常处理,或者使用异常处理制作不同的风格。我会确保在 Dispose() 抛出异常时记录/断点,但如果你不能阻止它,下一个最好的事情是确保问题不会蔓延。

于 2010-02-17T22:38:01.040 回答
0

在我的情况下,这是因为在关闭表单时线程访问 UI 元素。我通过在表单关闭时中止线程来解决它。(“FormClosing”事件)

FormClosing += (o, e) => worker.Abort();
于 2016-08-20T18:45:17.580 回答