最近,我正在研究一些关于没有丢弃的对象的棘手错误。
我在代码中找到了一些模式。据报道,有些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调用,代码将变得笨拙。
处理此问题的最佳做法是什么?
答案 0 :(得分:28)
确实泄漏了dispose方法的异常是非常糟糕的,特别是因为实现IDisposable的东西通常会指定一个调用Dispose的终结器。
问题在于通过处理异常来解决地毯下的问题可能会让您遇到一些非常难以调试的情况。如果您的IDisposable分配了一个仅在处置后才会被释放的关键部分,该怎么办?如果你忽略了异常发生的事实,你可能会陷入死锁中心。我认为Dispose中的失败应该是您希望尽早失败的情况之一,因此您可以在发现错误后立即修复错误。
当然这一切都取决于所处理的物体,对于某些物体,你可以恢复,而其他物体则不能。作为一般经验法则,Dispose在正确使用时不应抛出异常,并且您不必在要调用的嵌套Dispose方法中对异常进行防御性编码。
你真的不想在地毯下扫除OutOfMemoryException吗?
如果我有一个狡猾的第三方组件,任意在Dispose上抛出异常,我会修复它并将其托管在一个单独的进程中,我可以在它开始播放时拆除它。
答案 1 :(得分:3)
如果在终结上下文中调用Dispose()并抛出异常,则您的进程将被终止。
如果您怀疑Foo.Dispose()正在抛出异常,我会尽可能地将其丢弃,并将其包装在try / catch中。尽你所能在catch中摆脱它 - 将引用设置为null。从Dispose()抛出异常是非常糟糕的,应该避免。
不幸的是,如果这是错误的第三方代码,那么最好的办法是让他们修复它。你不应该在它之后手动清理。
希望有所帮助。
答案 2 :(得分:1)
因为您不必在using()语句中分配变量 - 为什么不为此使用'stacked'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 using语句可以在old Joe Duffy blog post中找到(参见“C#和VB Using Statement,C ++ Stack Semantics”一节)。 Joe Duffy帖子被IDisposable上的许多StackOverflow答案引用。我还发现了一个recent question,其中使用局部变量的语句堆叠似乎很常见。除了C# language spec(C#3.0规范中的8.13节)之外,我找不到finally块的链接,并且只针对单个“使用”块中的多个变量,这不是我提议的,但是如果你对IL进行分解,你会发现try / finally块是嵌套的。在空检查中,也来自C#规范:'如果获取了空资源,则不会调用Dispose,也不会抛出任何异常。'
答案 3 :(得分:1)
Dispose不应该抛出任何异常。如果它确实 - 它写得不好,那么......
try { some.Dispose(); } catch {}
应该足够了。
答案 4 :(得分:1)
According to Design Rules :
"A IDisposable.Dispose method should not throw an exception."
So if your program crashes because of unhandled exception from Dispose() - refer Official Solution
答案 5 :(得分: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方法的美观和干净。在我们的例子中,通常将处理指针置零,因此引用参数。
在您的情况下,您可能希望添加异常处理,或者通过异常处理创建不同的风格。每当Dispose()抛出异常时我都会确保你记录/断点,但是如果你不能阻止它,那么下一个最好的办法就是确保问题不会传播。
答案 6 :(得分:0)
在我的情况下,这是因为一个线程在关闭表单时访问UI元素。我通过在表格上关闭中止线程来重新取消它。 (&#34; FormClosing&#34;事件)
FormClosing += (o, e) => worker.Abort();