【问题标题】:C# Time of finally executionC#最终执行时间
【发布时间】:2010-10-27 10:56:40
【问题描述】:

获取此代码:

using System;

namespace OddThrow
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                throw new Exception("Exception!");
            }
            finally
            {
                System.Threading.Thread.Sleep(2500);
                Console.Error.WriteLine("I'm dying!");
                System.Threading.Thread.Sleep(2500);
            }
        }
    }
}

这给了我这个输出:

Unhandled Exception: System.Exception: Exception!
   at OddThrow.Program.Main(String[] args) in C:\Documents and Settings\username
\My Documents\Visual Studio 2008\Projects\OddThrow\OddThrow\Program.cs:line 14
I'm dying!

我的问题是:为什么未处理的异常文本出现在 finally 之前?在我看来,finally 应该在堆栈展开时被执行,甚至在我们知道这个异常未处理之前。请注意对 Sleep() 的调用 - 这些调用发生在 打印未处理的异常之后,就好像它正在执行以下操作一样:

  1. 未处理的异常文本/消息
  2. 终于阻止了。
  3. 终止应用程序

根据 C# 标准,§8.9.5,这种行为是错误的:

  • 在当前函数成员中,检查包含抛出点的每个 try 语句。对于每个语句 S,从最内层的 try 语句开始,到最外层的 try 语句结束,评估以下步骤:
    • 如果 S 的 try 块包含了抛出点,并且如果 S 有一个或多个 catch 子句,则按出现的顺序检查 catch 子句,以便为异常找到合适的处理程序。指定异常类型或异常类型的基类型的第一个 catch 子句被视为匹配项。一般的 catch 子句(第 8.10 节)被认为是任何异常类型的匹配项。如果找到匹配的 catch 子句,则通过将控制权转移到该 catch 子句的块来完成异常传播。
    • 否则,如果 S 的 try 块或 catch 块包围了 throw 点,并且如果 S 有 finally 块,则控制转移到 finally 块。如果 finally 块抛出另一个异常,则终止当前异常的处理。否则,当控制到达 finally 块的终点时,继续处理当前异常。
  • 如果在当前函数成员调用中未找到异常处理程序,则终止函数成员调用。然后对函数成员的调用者重复上述步骤,并使用与调用函数成员的语句相对应的抛出点。
  • 如果异常处理终止了当前线程中的所有函数成员调用,表明该线程没有异常的处理程序,则该线程本身终止。这种终止的影响是由实现定义的。

我哪里出错了? (我收到了一些自定义控制台错误消息,这是在路上。轻微的,只是烦人,让我质疑语言......)

【问题讨论】:

  • 很棒的问题。我会写一篇关于它的博客文章。在此之前,您可能会喜欢阅读以下内容,这些内容将帮助您了解这里发生的事情。这是一个艰难的过程,但值得。 blogs.msdn.com/cbrumme/archive/2003/10/01/51524.aspx
  • +1 只是因为 Eric Lippert 说这是一个好问题。

标签: c# exception finally


【解决方案1】:

标准关于执行顺序的陈述是正确的,并且与您所观察到的不矛盾。 “未处理的异常”消息允许出现在进程中的任何点,因为它只是来自 CLR 的消息,而不是真正的异常处理程序本身。关于执行顺序的规则只适用于在 CLR 内部执行的代码,而不适用于 CLR 本身所做的事情。

您实际上所做的是公开一个实现细节,即通过查看我们在其中的 try{} 块的堆栈来识别未处理的异常,而不是通过实际探索到根。通过查看此堆栈可能处理或不处理异常,但以这种方式识别未处理的异常。

您可能知道,如果您在 main 函数中放置一个顶级 try{}catch{},那么您将看到您所期望的行为:每个函数的 finally 将在检查下一帧之前执行匹配捕获{}。

【讨论】:

  • 您对顶级 try/catch 的看法是正确的——我只是希望不必这样做。看来我没那么幸运了。我想我只需要解决这个问题。不过我同意 - 这似乎是一条 CLR 消息,可能出于有用的目的,不符合异常处理程序的条件。
  • 是的-我认为那些结束词“实现定义”是这里的关键,也是我正在关注的。
【解决方案2】:

我阅读内容的方式可能有点离谱……但是您可以尝试 finally 块而没有捕获。

从您发布的描述来看,由于 Exception 从未被捕获,因此它会冒泡到调用者处,通过堆栈向上工作,最终未处理,调用终止,然后调用 finally 块。

【讨论】:

  • 我确实有一个尝试/最终没有捕获。这是设计使然。阅读我发布的标准部分 - 通过阅读它,finally() 块应该在堆栈展开时执行,而不是之后。
  • 我的阅读方式与您不同。在我看来,异常会冒泡直到它被处理(或者它通过未处理的堆栈向上工作......这在你的情况下正在发生),然后将执行 finally 块。
  • @Thanatos,通过该语句“finally() 块应该在堆栈展开时执行,而不是之后”,您表明,在您的示例中,“处理”未处理的异常发生在线程正在睡觉。
【解决方案3】:

输出实际上来自默认的 CLR 异常处理程序。异常处理程序发生在 finally 块之前。在 finally 块之后,CLR 由于未处理的异常而终止(它之前不能终止,因为 c# 保证 [1] 调用 finally 子句)。

所以我想说这只是标准行为,异常处理发生在 finally 之前。

[1] 在正常运行期间至少在没有内部运行时错误或断电的情况下保证

【讨论】:

  • 通过“异常处理程序发生在 finally 块之前” - 在嵌套的 try 中,内部 try 的 finally 在外部 try 的 catch 之前执行。我在这里期待类似的行为 - 事实上,这就是标准似乎所说的。
  • 我同意,你是对的。该标准甚至根本没有谈论默认的未处理异常处理程序。如果您向 AppDomain.CurrentDomain.UnhandledException 添加一个处理程序,这也会在 finally 之前执行
【解决方案4】:

要添加更多内容,请考虑以下几点:

using System;
namespace OddThrow
{
    class Program
    {
        static void Main()
        {
            AppDomain.CurrentDomain.UnhandledException +=
                delegate(object sender, UnhandledExceptionEventArgs e)
            {
                Console.Out.WriteLine("In AppDomain.UnhandledException");
            };
            try
            {
                throw new Exception("Exception!");
            }
            catch
            {
                Console.Error.WriteLine("In catch");
                throw;
            }
            finally
            {
                Console.Error.WriteLine("In finally");
            }
        }
    }
}

在我的系统(挪威)上显示:

[C:\..] ConsoleApplication5.exe
In catch
In AppDomain.UnhandledException

Ubehandlet unntak: System.Exception: Exception!
   ved OddThrow.Program.Main() i ..\Program.cs:linje 24
In finally

【讨论】:

    【解决方案5】:

    虽然不完全是预期的,但程序的行为确实如此。 finally 块不希望首先运行,它只希望始终运行。

    我调整了你的样本:

    public static void Main()
    {
        try
        {
            Console.WriteLine("Before throwing");
            throw new Exception("Exception!");
        }
        finally
        {
            Console.WriteLine("In finally");
            Console.ReadLine();
        }
    }
    

    在这种情况下,您将看到令人讨厌的未处理异常对话框,但随后控制台将输出并等待输入,从而执行 finally,而不是在 Windows 本身捕获未处理异常之前。

    【讨论】:

      【解决方案6】:

      不带catch 的try/finally 将使用默认处理程序,该处理程序与您看到的完全一样。我一直都在使用它,例如,在处理异常会覆盖错误但您仍然想做一些清理工作的情况下。

      还要记住输出到标准错误和标准输出是缓冲的。

      【讨论】:

      • 错误应该是无缓冲的 - 如果它对应于其他语言的 std::cerr/stderr/file #2。 (缓冲错误有什么好处——你的程序会因最后的错误消息而崩溃,可能位于缓冲区中)并且没有捕获的 try/finally 并不总是表现出这种行为——只有当异常一直传播到程序之外时.无捕获尝试是非常合法的(并且很常见)。
      • 没有抓住问题的重点。这与存在的“未处理异常”消息无关,而是关于事情发生的顺序。
      【解决方案7】:

      try-catch-finally 块的工作方式完全符合您的预期如果它们在某个时间点被捕获。当我为此编写一个测试程序并使用各种嵌套级别时,它的行为方式与您所描述的匹配的唯一情况是异常完全未被代码处理,并且它冒泡到操作系统。

      每次我运行它时,操作系统都会产生错误消息。 所以问题不在于 C#,而是用户代码未处理的错误不再受应用程序的控制,因此运行时(我相信)不能强制执行上面的图案。

      如果您创建了一个 Windows 窗体应用程序,并将所有消息写入文本框(然后立即刷新它们)而不是直接写入控制台,那么您根本不会看到该错误消息,因为它被插入到调用应用程序而不是您自己的代码的错误控制台。

      编辑

      我将尝试强调其中的关键部分。 未处理的异常不在您的控制范围内,您无法确定何时执行它们的异常处理程序。如果您在应用程序中在某个时间点捕获了异常,那么@987654321 @ 块将在堆栈中较低的 catch 块之前执行。

      【讨论】:

      • Sleep() 调用是为了向我保证 finally() 块确实发生在第二个,在任何打印“未处理的异常消息”之后,并且我没有在看某种我控制台上的 /O 缓冲/刷新(尽管错误应该是无缓冲的)。取出 Sleep() - 行为是一样的。
      【解决方案8】:

      将几个答案放在一起,发生的情况是,一旦您有一个未处理的异常,就会在 AppDomain 上引发一个 UnhandledExceptionEvent,然后代码继续执行(即 finally)。这是MSDN Article on the event

      【讨论】:

        【解决方案9】:

        下一次尝试:

        1. 我相信 c# 标准中没有提到这种情况,我同意它似乎几乎与它相矛盾。

        2. 我认为发生这种情况的内部原因有点像这样: CLR 将其默认异常处理程序作为 SEH 处理程序注册到 FS:[0] 当您的代码中有更多捕获时,这些处理程序将添加到 SEH 链中。或者,SEH处理时只调用CLR handler,内部处理CLR异常链,不知道是哪个。

        在您的代码中,当抛出异常时,只有默认处理程序位于 SEH 链中。在任何堆栈展开开始之前调用此处理程序。

        默认异常处理程序知道堆栈上没有注册异常处理程序。因此,它首先调用所有已注册的 UnhandledException 处理程序,然后打印其错误消息并将 AppDomain 标记为卸载。

        只有在堆栈展开甚至开始并且根据 c# 标准调用 finally 块之后。

        如我所见,c# 标准中没有考虑 CLR 处理未处理异常的方式,只考虑堆栈展开期间调用 finally 的顺序。保留此顺序。之后,“这种终止的影响是实现定义的”。条款生效。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-03-14
          • 2012-03-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多