代码之家  ›  专栏  ›  技术社区  ›  Thanatos

最终执行时间

  •  21
  • Thanatos  · 技术社区  · 15 年前

    采用此代码:

    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之前?在我看来,在我们开始之前,最后一个应该被逐出堆栈。 知道 此异常未处理。注意对sleep()的调用——这些都发生了 之后 打印未处理的异常,就好像它在执行以下操作:

    1. 未处理的异常文本/消息
    2. 最后块。
    3. 终止应用程序

    根据C_标准,_8.9.5,这种行为是错误的:

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

    我哪里出错了?(我收到了一些自定义控制台错误消息,这是一种阻碍。很小,很烦人,让我质疑语言……)

    10 回复  |  直到 15 年前
        1
  •  7
  •   kdt    15 年前

    标准中关于执行顺序的陈述是正确的,与你所观察到的不一致。“未处理的异常”消息允许出现在 任何 指向进程,因为它只是来自clr的消息,而不是异常处理程序本身。关于执行顺序的规则只适用于在clr内部执行的代码,而不适用于clr本身所做的。

    实际上,您所做的是公开一个实现细节,即通过查看一个堆栈来识别未处理的异常,该堆栈尝试块我们在其中,而不是通过一路深入到根目录来识别。例外可能是也可能不是 处理 通过查看此堆栈,可以识别未处理的异常。

    正如您可能意识到的,如果您在主函数中放置一个顶级的try catch,那么您将看到您期望的行为:在检查下一帧是否匹配catch之前,将最终执行每个函数。

        2
  •  2
  •   Justin Niessner    15 年前

    我读东西的方式可能有点离谱……但是你有一个尝试性的最后一个街区,没有一个陷阱。

    根据您发布的描述判断,由于从未捕捉到异常,它会冒泡到调用方,在堆栈中向上工作,最终未处理,调用终止,然后调用finally块。

        3
  •  2
  •   Ben Schwehn    15 年前

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

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

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

        5
  •  2
  •   Lasse V. Karlsen    15 年前

    要在混合物中添加更多,请考虑:

    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
    
        6
  •  1
  •   Dries Van Hansewijck    15 年前

    虽然不是完全预期的,但程序确实按其应该的方式运行。finally块不应首先运行,只应始终运行。

    我调整了你的样品:

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

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

        7
  •  0
  •   Shea    15 年前

    没有catch的try/finally将使用默认的处理程序,该处理程序执行您看到的操作。我一直在使用它,例如,在处理异常时会覆盖一个错误,但您仍然需要做一些清理。

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

        8
  •  0
  •   DevinB    15 年前

    Try-Catch Finally块的工作方式与您预期的完全一致 如果他们在某个地方被抓到 .当我为此编写了一个测试程序,并且使用了各种嵌套级别时,它的行为方式与您所描述的一致,唯一的情况是异常完全由代码处理,并且冒泡到操作系统。

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

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

    编辑

    我会尽量强调其中的关键部分。 未处理的异常超出您的控制范围,您无法确定何时执行它们的异常处理程序。 如果你抓住了例外 在某一时刻 在您的应用程序中,然后 finally 块将在堆栈中较低层之前执行 catch 块。

        9
  •  0
  •   Andrew Cox    15 年前

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

        10
  •  0
  •   Ben Schwehn    15 年前

    下一步:

    1. 我相信这件事在C标准中没有提到,我同意它似乎几乎与之相矛盾。

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

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

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

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

    正如我看到的,在C标准中,不会考虑clr处理未处理异常的方式,只考虑堆栈展开期间调用finallys的顺序。此订单保留。在此之后,“此类终止的影响被定义为实施。”条款生效。