249

我正在为一位朋友查看一些代码,并说他在 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
  }
}
4

12 回答 12

280

简单的回答:是的。

于 2008-12-05T20:50:47.030 回答
209

通常,是的。finally 部分保证执行任何发生的事情,包括异常或返回语句。此规则的一个例外是线程 ( OutOfMemoryException, StackOverflowException) 上发生的异步异常。

要了解有关这种情况下的异步异常和可靠代码的更多信息,请阅读受约束的执行区域

于 2008-12-05T20:53:23.157 回答
158

这是一个小测试:

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
于 2008-12-05T21:45:00.803 回答
40

引用 MSDN

finally用于保证语句块的执行,而不管前面的try块是如何退出的。

于 2008-12-05T21:03:42.057 回答
20

通常是的,finally 会运行。

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

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

以下场景,finally 不会运行:

异步 StackOverflowException。
从 .NET 2.0 开始,堆栈溢出将导致进程终止。finally 不会运行,除非应用进一步的约束以使 finally 成为 CER(受约束的执行区域)。CER 不应用于一般用户代码。它们只应在清理代码始终运行至关重要的情况下使用 - 在所有进程无论如何都会因堆栈溢出而关闭并且因此默认情况下将清理所有托管对象之后。因此,CER 唯一相关的地方是分配在进程之外的资源,例如非托管句柄。

通常,非托管代码在被用户代码使用之前由某个托管类包装。托管包装类通常会使用 SafeHandle 来包装非托管句柄。SafeHandle 实现了一个关键终结器和一个在 CER 中运行的 Release 方法,以保证清理代码的执行。出于这个原因,您不应该看到 CER 散布在整个用户代码中。

因此,finally 不在 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.
}
于 2011-06-23T15:09:38.333 回答
14

有一个非常重要的例外,我在任何其他答案中都没有提到过,而且(在 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();
}

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

于 2018-01-29T14:36:10.043 回答
8

我意识到我迟到了,但在确实抛出异常的情况下(不同于 OP 的示例)MSDN 状态(https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx):“如果没有捕获到异常,finally块的执行取决于操作系统是否选择触发异常展开操作。”

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

于 2015-02-26T08:25:46.027 回答
3

是的。这实际上是 finally 语句的要点。除非发生灾难性事件(内存不足、计算机拔掉电源等),否则应始终执行 finally 语句。

于 2008-12-05T20:54:23.903 回答
3

它也不会触发未捕获的异常并在 Windows 服务中托管的线程中运行

在 Windows 服务中运行的线程中时不执行 finally

于 2015-07-01T12:33:47.287 回答
2

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

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

结果将是:尝试

于 2014-07-03T06:18:36.507 回答
0

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

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

于 2015-12-16T12:47:31.747 回答
0

finally 块的主要目的是执行其中写入的任何内容。它不应该依赖于 try 或 catch 中发生的任何事情。但是使用 System.Environment.Exit(1) 应用程序将退出而不移动到下一行代码。

于 2016-04-14T12:41:31.670 回答