C#最终执行的时间

拿这个代码:

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! 

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

  1. 未处理的exception文本/消息
  2. 最后块。
  3. 终止申请

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

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

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

标准关于执行顺序的陈述是正确的,并且与您观察的内容不一致。 允许“未处理exception”消息出现在进程中的任何位置,因为它只是来自CLR的消息,实际上并不是exception处理程序本身。 关于执行顺序的规则仅适用于在CLR内执行的代码,而不适用于CLR本身所执行的操作。

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

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

我可以在我正在读东西的方式离开基地…但你有一个尝试终于阻止没有捕获。

根据您发布的描述,由于exception从未被捕获,它会向调用者冒泡,在堆栈中向上运行,最终未处理,调用终止,然后调用finally块。

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

所以我说这只是标准行为,exception处理最终发生。

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

要在混音中添加更多内容,请考虑以下事项:

 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 

虽然没有完全预料到,但程序确实表现得很好。 预计finally块不会先运行,只能运行它。

我调整了你的样本:

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

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

没有catch的try / finally将使用默认处理程序,它完全按照您的看法执行。 我一直都在使用它,例如,在处理exception会覆盖错误的情况下,但是仍然需要进行一些清理。

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

如果在某些时候被捕获, try-catch-finally块正如您预期的那样工作。 当我为此编写一个测试程序,并且我使用各种嵌套级别时,唯一的情况是它的行为与您描述的相匹配,当代码完全未处理exception时,它会冒泡到操作系统。

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

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

编辑

我将尝试突出其中的关键部分。 未处理的exception超出您的控制范围,您无法确定何时执行其exception处理程序。 如果在应用程序的某个时刻捕获exception,那么finally块将在堆栈中的catch块之前执行。

要将几个答案放在一起,会发生的情况是,只要您有一个未处理的exception,就会在AppDomain上引发UnhandledExceptionEvent,然后代码继续执行(即最终)。 这是关于该事件的MSDN文章

接下来尝试:

  1. 我相信在c#标准中没有提到这个案例,我同意它似乎几乎与它相矛盾。

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

在抛出exception的代码中,只有默认处理程序在SEH链中。 在任何堆栈展开开始之前调用此处理程序。

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

只有在堆栈展开后才开始,最后根据c#标准调用块。

正如我所看到的,CLR处理未处理exception的方式不在c#标准中考虑,只考虑在堆栈展开期间调用finally的顺序。 此订单已保留。 之后,“此类终止的影响是实现定义的”。 子句生效。