【问题标题】:If I return out of a try/finally block in C# does the code in the finally always run?如果我从 C# 中的 try/finally 块返回,finally 中的代码是否总是运行?
【发布时间】:2012-05-02 14:27:35
【问题描述】:

根据一些初步测试,它似乎确实如此,但我想知道它是否保证返回,或者在某些情况下它不能返回?这对我的应用程序至关重要,但我还没有找到不会返回的用例。
我想获得这方面的专业知识。

【问题讨论】:

标签: c# try-catch-finally


【解决方案1】:

在正常情况下,无论 try 或 catch 块内发生什么,都会执行 finally 块中的代码。从方法返回与否都没关系。

在某些情况下这是不正确的。例如,如果 finally 块中的代码抛出异常,那么它将像任何其他代码块一样停止执行。

Eric Lippert 写了一个更全面的答案,概述了其他案例:https://stackoverflow.com/a/10260233/53777

关于 goto,答案仍然是肯定的。考虑以下代码:

try
{
    Console.WriteLine("Inside the Try");
    goto MyLabel;
}
finally
{
    Console.WriteLine("Inside the Finally");
}

MyLabel:
    Console.WriteLine("After the Label");

产生的输出是这样的:

内测

在最后的内部

标签之后

【讨论】:

    【解决方案2】:

    如果出现终止应用程序的致命异常,将不会调用 finally 块。包括堆栈溢出、JIT 调用方法期间的异常、CLR 运行时中的致命异常。

    正如@mintech 指出的那样,如果应用程序挂在块内,它根本不会到达 finally 块。这包括等待同步对象、死锁无限循环甚至是无法关闭它的 UI。

    【讨论】:

      【解决方案3】:

      其他答案有许多不准确之处。

      当控制离开try块正常时,控制被传递给finally块——也就是说,通过return、goto、break、continue或简单地从结尾处掉下来。当控制离开try 块通过一个被封闭的catch 块捕获的异常 时,控制被传递给finally 块。

      在所有其他情况下,都没有保证 finally 块中的代码将被调用。特别是:

      • 如果 try 块代码进入无限循环,或者线程被冻结且从未解冻,则 finally 块代码永远不会被调用。

      • 如果进程在调试器中暂停,然后主动终止,则永远不会调用 finally 块。如果进程执行了快速失败,则永远不会调用 finally 块。

      • 如果电源线从墙上拔出,则永远不会调用 finally 块。

      • 如果抛出异常没有相应的catch块,那么finally块是否运行是运行时的实现细节。当存在未捕获的异常时,运行时可以选择任何行为。 “不运行 finally 块”和“运行 finally 块”都是“任何行为”的示例,因此可以选择其中任何一个。通常,运行时会询问用户是否要在 finally 块运行之前附加调试器;如果用户说不,那么 finally 块运行。但同样:运行时不是必需来做到这一点的。它可能会很快失败。

      你不能依赖 finally 块总是被调用。如果你需要一个强有力的代码执行保证,那么你不应该写一个 try-finally,你应该写一个constrained execution region。正确编写 CER 是 C# 编程中最困难的任务之一,因此在尝试编写代码之前请仔细阅读文档。

      顺便说一句,关于最终被阻止的 goto 的“有趣事实”是:

      try { goto X; } finally { throw y; } 
      X : Console.WriteLine("X");
      

      X 是一个不可访问的标签,由可访问的 goto 定位!所以下次你参加聚会时,你可能会说“大家好,任何人都可以制作一个 C# 程序,该程序有一个不可访问的标签,并以可访问的 goto 为目标吗?”你会看到聚会上谁阅读了可达性规范,谁没有!

      【讨论】:

      • 所以这个故事的寓意是:永远不要邀请 Eric Lippert 参加你的派对;)
      • @Tergiver:鲜为人知的事实:编译器开发人员举办最佳派对。
      • @qqqqqqq:但这里真的很讨厌。假设您有 void X() { try { ObtainAdminPowers(); DoSomethingDangerous(); } finally { ReleaseAdminPowers(); }}DoSomethingDangerous 抛出异常。现在假设我们有try { X(); } catch (Exception) when (M()) { }}。方法M() 运行 管理员权限被释放! X 的作者认为除了DoSomethingDangerous 之外没有任何代码可以使用管理员权限,但作者错了!
      • @qqqqqqq:在这种情况下,正确的缓解措施是荒谬的void X() { try { Obtain(); DoIt(); } catch { Release(); throw; } Release(); },任何明智的人都不会写。这是 .NET 异常中令人遗憾的安全设计缺陷之一。
      • @EricLippert 可以说根本原因是try { M } finally { N }{ try { M } catch (Exception e) { N; throw; } N } 的含义不同。我理解动机,但不禁想知道如果不引入完全独立的异常相关原语是否可以实现。
      【解决方案4】:

      这里有一些例子:

      Environment.FailFast()

              try
              {
                  Console.WriteLine("Try");
                  Environment.FailFast("Test Fail");
      
              }
              catch (Exception)
              {
                  Console.WriteLine("catch");
              }
              finally
              {
                  Console.WriteLine("finally");
              }
      

      输出只有“Try”

      堆栈溢出

              try
              {
                  Console.WriteLine("Try");
                  Rec();
              }
              catch (Exception)
              {
                  Console.WriteLine("catch");
              }
              finally
              {
                  Console.WriteLine("finally");
              }
      

      Rec 在哪里:

          private static void Rec()
          {
              Rec();
          }
      

      输出仅为“Try”,进程因 StackOverflow 而终止。

      未经处理的异常

              try
              {
                  Console.WriteLine("Try");
                  throw new Exception();
              }
              finally
              {
                  Console.WriteLine("finally");
              }
      

      【讨论】:

      • 最后一个例子(Unhanded exception)实际上执行了finally块。
      猜你喜欢
      • 1970-01-01
      • 2017-07-20
      • 1970-01-01
      • 1970-01-01
      • 2010-10-01
      • 2011-02-18
      • 1970-01-01
      相关资源
      最近更新 更多