Will Try / Finally(没有Catch)会使异常冒泡吗?

134

我非常肯定答案是肯定的。如果我使用Try Finally块但不使用Catch块,则任何异常都将冒泡。正确吗?

对于这种做法有什么想法吗?

Seth


那么,“bubble”是什么?它会抛出还是隐藏? - Daniel Möller
2个回答

155

没错,它肯定会生效。当然,前提是你的 finally 块不会抛出异常,在这种情况下,它实际上会“替换”最初抛出的异常。


14
@David:在 C# 中,你无法从 finally 块中返回。 - Jon Skeet
3
MSDN文档也证实了这一答案:或者,您可以在调用堆栈更高的try-finally语句的try块中捕获可能抛出的异常。也就是说,您可以在调用包含try-finally语句的方法的方法中捕获异常,或者在调用该方法的方法中捕获异常,或者在调用堆栈中的任何方法中捕获异常。如果未捕获异常,则最终块的执行取决于操作系统是否选择触发异常解绑定操作。 - broadband

66

对于这种做法,你有什么想法吗?

有的。一定要小心谨慎。当finally块正在运行时,完全有可能是因为抛出了一个未处理的、意外的异常。这意味着有些东西出了问题,并且可能发生完全意外的事情

在这种情况下,可以说你根本不应该在finally块中运行代码。finally块中的代码可能被构建为假设其依赖的子系统是健康的,而实际上它们可能已经深度损坏了。finally块中的代码可能会使情况变得更糟。

例如,我经常看到这种情况:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

这段代码的作者在想:“我对世界状态做了一次临时变更,需要将状态恢复到调用之前的状态。” 但是让我们来思考一下可能会发生什么问题。

首先,资源访问可能已被调用方禁用;在这种情况下,该代码重新启用它,可能过早地启用。

其次,如果DoSomethingToTheResource抛出异常,那么启用资源访问是正确的吗?管理资源的代码意外破坏了。实际上,该代码的含义是“如果管理代码出错,请确保其他代码尽快调用该错误代码,以便使其也彻底失败”。这似乎不是一个好主意。

第三,如果DoSomethingToTheResource抛出异常,那么我们怎么知道EnableAccessToTheResource也不会抛出异常呢?任何困扰使用资源的可怕情况也可能影响清理代码,在这种情况下,原始异常将丢失,问题将更难以诊断。

我倾向于编写不使用try-finally块的代码:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();
现在状态只有在必要时才会被更改。现在,调用者的状态不会被搞乱。如果DoSomethingToTheResource失败,我们现在不会重新启用访问权限。我们假设存在严重问题,不冒试图继续运行代码而使情况更糟的风险。如果可能的话,由调用方解决这个问题。
那么什么时候运行finally块是一个好主意呢?首先,当异常是可以预期的时候。例如,你可能预计尝试锁定文件可能会失败,因为其他人已经锁定了它。在这种情况下,捕获异常并将其报告给用户是有意义的。在这种情况下,对于错误的不确定性减少了;清理工作不太可能使事情变得更糟。
第二,当您清理的资源是一种稀缺的系统资源时。例如,在finally块中关闭文件句柄是有意义的。(使用“using”当然只是编写try-finally块的另一种方式。)文件的内容可能是损坏的,但是现在你无能为力。文件句柄最终会被关闭,所以最好尽快关闭。

8
我们行业在错误处理方面仍然没有做好准备,这里有更多的例子。虽然我没有比异常处理更好的建议,但我希望未来会出现更容易采取正确行动的解决方案。 - Jon Skeet
在vb.net中,Finally块中的代码可以知道哪个异常是待处理的。如果设置一个异常层次结构来区分“没做到;状态还好”和“状态已经破损”,那么只有在待处理的异常不是其中之一时才能运行Finally块。我喜欢让释放器/清理例程捕获异常并抛出DisposerFailedException(这是“坏”类别中的异常,并包括原始异常作为InnerException)。我希望看到一个标准的iDisposableEx接口,其中包含一个Dispose(Ex as Exception)以促进这一过程。 - supercat
虽然我理解在对象处于错误状态并尝试清理可能会使情况变得更糟的情况下,异常可能会发生,但我认为这些问题应该在清理代码中处理,可能需要使用委托来实现。例如,锁包装器可以公开一个“CheckIfSafeToUnlock(Ex as Exception)”函数委托,在锁定对象处于无效状态时设置它,并在其他情况下清除它。在释放锁之前,包装器可以检查委托;如果不为空,则可以运行它并仅在返回true时释放锁。 - supercat
使用这样的委托来包装将允许操作对象状态的代码选择适当的清理操作,如果它被中断。这些操作可能包括修复数据结构并返回True,抛出另一个“更严重”的异常来包装原始异常,或者让原始异常上升而返回False。 - supercat
2
Eric,感谢你的出色回答。我想我应该问两个问题,因为你和Jon都是正确的。无论如何,你得到了赞同。感谢你对问题的关注。 - Seth Spearman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接