如果我在Try块中返回值,那么Final语句中的代码会触发吗?


237

我正在查看一个朋友的一些代码,并说他在try-finally块中使用了return语句。即使try块的其余部分没有触发,Finally部分中的代码是否仍会触发?

例:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}


在这里被处理意味着:被抓住。即使全局处理程序中只有一个空的catch块也足够。另外,还有一些无法处理的异常:StackOverflowExceptionExecutionEngineException其中一些。而且由于无法处理它们,因此finally不会运行。
亚伯

8
@Abel:您似乎在谈论另一种情况。这个问题是关于返回try块。程序突然中止没有任何事情。
乔恩·斯基特

6
@Abel:我不确定“异常后返回”是什么意思,但这似乎不是这里所要问的。看一下代码- try块的第一条语句是一条return语句。(无法访问该块的第二个语句,并且会生成警告。)
Jon Skeet

4
@Abel:确实,如果问题是“ finally语句中的代码将始终在每种情况下执行”,那将是相关的。但这不是要问的。
乔恩·斯基特

Answers:


265

简单的答案:是的。


9
@Edi:嗯,我不知道后台线程和它有什么关系。基本上,除非整个过程中止,否则该finally块将执行。
乔恩·斯基特

3
长的答案是,如果发生了灾难性的事情,例如堆栈溢出,内存不足异常,某种严重的崩溃,或者如果有人在正确的时间拔掉了计算机的电源,您不一定会运行finally块。但是出于所有意图和目的,除非您做的事情非常非常错误,否则finally块总是会触发。
安德鲁·罗尔斯

如果尝试之前的代码由于某种原因而失败,我注意到最终仍然可以执行。在这种情况下,您可以为条件finally添加一个条件,该条件仅在成功执行以下代码时满足条件才能最终执行
kjosh


154

这是一个小测试:

class Class1
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("before");
        Console.WriteLine(test());
        Console.WriteLine("after");
    }

    static string test()
    {
        try
        {
            return "return";
        }
        finally
        {
            Console.WriteLine("finally");
        }
    }
}

结果是:

before
finally
return
after

10
@CodeBlend:它与WriteLine实际执行方法调用的结果有关。在这种情况下,return调用将设置方法结果,最后在方法退出之前写入控制台。然后WriteLineMain方法中的in从返回调用中吐出文本。
NotMe 2013年

38

从MSDN报价

最后用于保证无论前面的try块如何退出,代码的语句块都能执行。


3
好吧,除了Mehrdad的特殊情况外,如果您在try块中进行调试,然后停止调试:),那么最终也不会被调用。看来生活没有任何保证。
StuartLC

1
并非完全如此,引用MSDN的话但是,如果未处理异常,则finally块的执行取决于如何触发异常展开操作。反过来,这取决于计算机的设置方式。
亚伯

1
@Abel在这里提出了一个关键点。如果例如通过任务管理器中止程序,每个人都可以看到如何不执行finally块。但是,从目前的角度来看,这个答案是完全错误的:finally实际上,即使对于完全普通的程序(碰巧抛出此异常),也不保证任何内容。
彼得-恢复莫妮卡

19

通常是的,最终将运行。

对于以下三种情况,最终将始终运行:

  1. 没有例外发生
  2. 同步异常(正常程序流中发生的异常)。
    这包括从System.Exception派生的符合CLS的异常和不从System.Exception派生的不符合CLS的异常。不符合CLS的异常由RuntimeWrappedException自动包装。C#不能引发非CLS投诉异常,但是C ++这样的语言可以。C#可能会调用以可能引发不符合CLS规范的异常的语言编写的代码。
  3. 异步ThreadAbortException
    从.NET 2.0开始,ThreadAbortException将不再阻止finally运行。现在将ThreadAbortException提升到finally之前或之后。只要在线程中止发生之前实际输入了try,finally就会一直运行,并且不会被线程中止中断。

在以下情况下,最终将无法运行:

异步StackOverflowException。
从.NET 2.0开始,堆栈溢出将导致进程终止。除非应用了进一步的约束以使最终CER(受约束执行区域)生效,否则最终将不会运行。CER不应在一般用户代码中使用。仅应在始终运行清除代码的关键位置使用它们-无论如何,在所有进程都因堆栈溢出而关闭后,默认情况下将清除所有托管对象。因此,CER唯一相关的地方是在流程之外分配的资源,例如非托管句柄。

通常,非托管代码在被用户代码使用之前被某些托管类包装。托管包装器类通常将使用SafeHandle包装非托管句柄。SafeHandle实现了一个关键的终结器,以及一个在CER中运行的Release方法,以确保执行清理代码。因此,您不应看到CER在整个用户代码中乱七八糟。

因此,最终不会在StackOverflowException上运行的事实对用户代码没有任何影响,因为该过程无论如何都会终止。如果在某些情况下确实需要清除SafeHandle或CriticalFinalizerObject之外的某些非托管资源,请按以下方式使用CER;但是请注意,这是一种不好的做法-非托管概念应通过设计抽象为托管类和适当的SafeHandle。

例如,

// No code can appear after this line, before the try
RuntimeHelpers.PrepareConstrainedRegions();
try
{ 
    // This is *NOT* a CER
}
finally
{
    // This is a CER; guaranteed to run, if the try was entered, 
    // even if a StackOverflowException occurs.
}

请注意,对于国有企业,即使是CER也不会运行。在您撰写本文时,有关CER的MSDN文档是错误的/不完整的。SOE将在FailFast内部触发。我设法捕捉到这些的唯一方法是自定义CLR运行时托管。请注意,您的观点对其他一些异步异常仍然有效。
亚伯

10

有一个非常重要的例外,我在其他任何答案中都没有提到过,而且(在用C#编程18年之后)我不敢相信自己不知道。

如果您在块内引发或触发任何类型的异常catch(不仅仅是怪异的StackOverflowExceptions东西),而且整个try/catch/finally块内都不存在另一个try/catch块,则您的finally块将不会执行。这很容易证明-如果我自己没看过,考虑到我经常读到它只是很奇怪的,微小的极端情况,会导致finally块无法执行,我不会相信的。

static void Main(string[] args)
{
    Console.WriteLine("Beginning demo of how finally clause doesn't get executed");
    try
    {
        Console.WriteLine("Inside try but before exception.");
        throw new Exception("Exception #1");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Inside catch for the exception '{ex.Message}' (before throwing another exception).");
        throw;
    }
    finally
    {
        Console.WriteLine("This never gets executed, and that seems very, very wrong.");
    }

    Console.WriteLine("This never gets executed, but I wasn't expecting it to."); 
    Console.ReadLine();
}

我敢肯定这是有原因的,但是奇怪的是它的知名度还没有广为人知。(例如,此处已注明,但在此特定问题中没有任何地方。)


好吧,该finally区块根本无法抓住该catch区块。
彼得-恢复莫妮卡

7

我意识到我参加聚会很晚,但是(与OP的示例不同)在这种情况下,确实引发了MSDN状态(https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx)的异常:“如果未捕获到异常,则finally块的执行取决于操作系统是否选择触发异常展开操作。”

只有在调用堆栈后面的某些其他函数(例如Main)捕获到异常时,才能确保执行finally块。这个细节通常不是问题,因为所有运行时环境(CLR和OS)C#程序都在进程退出时(文件句柄等)使用其拥有的大部分资源来运行。在某些情况下,这可能很关键:要提交响应的数据库操作正在进行中。放松; 或某些可能无法被操作系统自动关闭并随后阻止服务器的远程连接。


3

是。实际上,这就是最后声明的重点。除非发生某种分类(内存不足,拔出计算机等),否则应始终执行finally语句。


1
我不同意。cf. 我的答案。finally如果从未捕获到异常,则try块中一个完全正常的异常足以绕过该块。那让惊讶;-)。
彼得-恢复莫妮卡

@ PeterA.Schneider-很有意思。当然,其含义并没有真正改变。如果调用堆栈中没有任何内容将捕获异常,则这意味着(如果我理解正确的话)意味着该过程即将终止。因此,这类似于即插即用的情况。我想从中得出的结论是,您应该始终具有顶级或未处理的异常捕获。
Jeffrey L Whitledge,

确实,这也是我的理解。我也感到惊讶。正确:最常见的资源将自动释放,特别是内存和打开的文件。但是操作系统不知道的资源(例如,打开服务器或数据库连接)可能会在远程端保持打开状态,因为它们从未被正确关闭过。套接字一直徘徊,等等。我想拥有一个顶级异常处理程序是明智的,是的。
彼得-恢复莫妮卡


2

如果您使用System.exit(0)从应用程序退出,最终将不会运行;如

try
{
    System.out.println("try");
    System.exit(0);
}
finally
{
   System.out.println("finally");
}

结果将是:试试


4
您以错误的语言回答,我想这是关于c#,但这似乎是Java。除此之外,在大多数情况下,这System.exit()是设计不良的提示:-)
z00l

0

在99%的方案中,可以保证该finally块内的代码将运行,但是,请考虑以下方案:您的线程中有一个try-> finally块(no catch),并且在该线程中得到未处理的异常。在这种情况下,线程将退出并且finally块将不会执行(在这种情况下,应用程序可以继续运行)

这种情况很少见,但这只是表明答案并非总是“是”,大多数情况下是“是”,有时在极少数情况下是“否”。


0

finally块的主要目的是执行其中编写的任何内容。它不应该依赖于尝试或捕获中发生的任何事情。但是,使用System.Environment.Exit(1),应用程序将退出而无需移至下一行代码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.