finally 块没有在 .NET 4.0 上运行,为什么?

发布于 2024-10-02 12:31:32 字数 1003 浏览 8 评论 0原文

好吧,这是一个奇怪的问题,我希望有人能提供一些线索。我有以下代码:

static void Main(string[] args)
{
    try
    {
        Console.WriteLine("in try");
        throw new EncoderFallbackException();
    }
    catch (Exception)
    {
        Console.WriteLine("in Catch");
        throw new AbandonedMutexException();
    }
    finally
    {
        Console.WriteLine("in Finally");
        Console.ReadLine();
    }
}

现在,当我将其编译到目标 3.5 (2.0 CLR) 时,它将弹出一个窗口,显示“XXX 已停止工作”。如果我现在单击取消按钮,它将运行最后,如果我等到它完成查找并单击关闭程序按钮它也会运行finally

现在有趣且令人困惑的是,如果我对 4.0 进行同样的编译,单击“取消”按钮将运行“最后”块,然后单击“关闭程序” 按钮不会。

我的问题是:为什么点击关闭程序按钮时finally在2.0上运行而不是在4.0上运行?这会造成什么后果?

编辑:我在 Windows 7 32 位上的发布模式(内置发布模式)下从命令提示符运行此命令。错误消息:下面的第一个结果是在 Windows 查找问题后在 3.5 上运行,点击关闭,第二个结果是当我在 4.0 上运行它并执行相同的操作时。

屏幕截图

Ok this is kind of a weird issue and I am hoping someone can shed some light. I have the following code:

static void Main(string[] args)
{
    try
    {
        Console.WriteLine("in try");
        throw new EncoderFallbackException();
    }
    catch (Exception)
    {
        Console.WriteLine("in Catch");
        throw new AbandonedMutexException();
    }
    finally
    {
        Console.WriteLine("in Finally");
        Console.ReadLine();
    }
}

Now when I compile this to target 3.5 (2.0 CLR) it will pop up a window saying "XXX has stopped working". If I now click on the Cancel button it will run the finally, and if I wait until it is done looking and click on the Close Program button it will also run the finally.

Now what is interesting and confusing is if I do the same thing compiled against 4.0, clicking on the Cancel button will run the finally block and clicking on the Close Program button will not.

My question is: Why does the finally run on 2.0 and not on 4.0 when hitting the Close Program button? What are the repercussions of this?

Edit: I am running this from a command prompt in release mode(built in release mode) on windows 7 32 bit. Error Message: First Result below is running on 3.5 hitting close after windows looks for issue, second is when I run it on 4.0 and do the same thing.

Screenshot

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

我现在能够重现该行为(当我第一次阅读您的问题时,我没有从您的问题中得到确切的步骤)。

我观察到的一个区别在于 .NET 运行时处理未处理异常的方式。 CLR 2.0 运行一个名为 Microsoft .NET Error Reporting Shim (dw20.exe) 的帮助程序,而 CLR 4.0 则启动 Windows 错误报告 (dw20.exe) >WerFault.exe)。

我认为两者在终止崩溃进程方面有不同的行为。 WerFault.exe 显然会立即终止 .NET 进程,而 .NET 错误报告 Shim 会以某种方式关闭应用程序,以便finally 块仍然被执行。

另请查看事件查看器:WerFault 记录了一个应用程序错误,通知崩溃的进程已终止:

Application: ConsoleApplication1.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Threading.AbandonedMutexException
Stack:
   at Program.Main(System.String[])

dw20.exe 但是仅将事件 ID 为 1001 的信息项记录到事件日志中,而不会终止该进程。

I am able to reproduce the behavior now (I didn't get the exact steps from your question when I was reading it the first time).

One difference I can observe is in the way that the .NET runtime handles the unhandled exception. The CLR 2.0 runs a helper called Microsoft .NET Error Reporting Shim (dw20.exe) whereas the CLR 4.0 starts Windows Error Reporting (WerFault.exe).

I assume that the two have different behavior with respect to terminating the crashing process. WerFault.exe obviously kills the .NET process immediately whereas the .NET Error Reporting Shim somehow closes the application so that the finally block still is executed.

Also have a look at the Event Viewer: WerFault logs an application error notifying that the crashed process was terminated:

Application: ConsoleApplication1.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Threading.AbandonedMutexException
Stack:
   at Program.Main(System.String[])

dw20.exe however only logs an information item with event id 1001 to the Event Log and does not terminate the process.

猛虎独行 2024-10-09 12:31:32

想想那种情况是多么糟糕:发生了一些意想不到的事情,却没有人编写代码来处理。在这种情况下,运行更多代码是否正确,这些代码可能不是为处理这种情况而构建的?可能不是。通常,正确的做法是尝试运行finally 块,因为这样做会使情况变得更糟。你已经知道进程正在下降;立即让它脱离痛苦。

在未处理的异常将导致进程崩溃的情况下,任何事情都可能发生。在这种情况下会发生什么是实现定义的:是否将错误报告给 Windows 错误报告、调试器是否启动等等。 CLR 完全有权利尝试运行finally 块,也完全有权利快速失败。在这种情况下,所有的赌注都将落空。不同的实现可以选择做不同的事情。

Think about how awful that situation is: something unexpected has happened that no one ever wrote code to handle. Is the right thing to do in that situation to run even more code, that was probably also not built to handle this situation? Possibly not. Often the right thing to do here is to not attempt to run the finally blocks because doing so will make a bad situation even worse. You already know the process is going down; put it out of its misery immediately.

In a scenario where an unhandled exception is going to take down the process, anything can happen. It is implementation-defined what happens in this case: whether the error is reported to Windows error reporting, whether a debugger starts up, and so on. The CLR is perfectly within its rights to attempt to run finally blocks, and is also perfectly within its rights to fail fast. In this scenario all bets are off; different implementations can choose to do different things.

晨曦÷微暖 2024-10-09 12:31:32

我关于这个主题的所有知识都来自这篇文章:CLR 全面深入 - CLR 中未处理的异常处理。请注意,它是为 .NET 2.0 编写的,但我有一种感觉,它对于我们在本例中所经历的情况是有意义的(无论如何,不​​仅仅是“因为它决定这样做”)。

快速回答“我没有时间读那篇文章”(尽管你应该这样做,这是一篇非常好的文章):

问题的解决方案(如果你绝对必须运行finally块)将是a)放入全局错误处理程序或 b) 强制 .NET 始终运行 finally 块并按照 .NET 1.1 中的方式(可以说是错误的方式)执行操作 - 将以下内容放入您的 app.config 中:

<legacyUnhandledExceptionPolicy enabled="1">

这样做的原因:
当 .NET 中抛出异常时,它开始回溯堆栈寻找异常处理程序,当找到异常处理程序时,它会在运行内容之前运行finally块第二次回溯堆栈。抓住。如果它没有找到catch,那么第二次遍历永远不会发生,因此finally块永远不会在这里运行,这就是为什么全局异常处理程序将始终运行finally子句,因为CLR将在找到catch时运行它们,而不是在运行它时运行它们(我相信这意味着即使你执行 catch/ throw 你的finally 块仍然会运行)。

app.config 修复起作用的原因是,对于 .NET 1.0 和 1.1,CLR 中有一个全局捕获,它会在异常变为非托管之前吞掉异常,这当然会触发 finally 块运行。当然,框架无法充分了解所述异常来处理它,例如堆栈溢出,因此这可能是错误的方法。

接下来是有点棘手的地方,我根据本文所说的内容做出假设。

如果您使用的是 .NET 2.0+,而没有启用旧版异常处理,那么您的异常将落入 Windows 异常处理系统 (SEH),这看起来与 CLR 非常相似,因为它会遍历帧,直到无法处理为止。找到一个 catch,然后调用一系列称为未处理异常过滤器 (UEF) 的事件。这是一个您可以订阅的事件,但一次只能订阅一个事件,因此当有事件订阅时,Windows 会将之前存在的回调地址传递给它,从而允许您设置 UEF 链处理程序 - 但他们不必尊重该地址,他们应该自己调用该地址,但如果有人破坏了链条,咩,你就不会再得到错误处理。我假设这就是当您取消 Windows 错误报告时发生的情况,它破坏了 UEF 链,这意味着应用程序将立即关闭并且finally块不会运行,但是如果您让它运行到最后并关闭它,它将调用链中的下一个 UEF。 .NET 将注册一个 AppDomain.UnhandledException 被调用的地方(因此即使这个事件也不能保证),我认为这也是你调用finally块的地方 - 正如我所能的那样'如果您从不转换回 CLR,托管的finally 块将如何运行(本文不讨论这一点。)

All my knowledge on this subject is taken from this article here: CLR Inside Out - Unhandled Exception Processing In The CLR. Please note it is written for .NET 2.0, but I have a feeling it makes sense for what we were experiencing in this case (more than "because it decided to" anyways).

Quick "I don't have time to read that article" answer (although you should, it's a really good one):

The solution to the problem (if you absolutely HAVE to have your finally blocks run) would be to a) put in a global error handler or b) force .NET to always run finally blocks and do things the way it did (arguably the wrong way) in .NET 1.1 - Place the following in your app.config:

<legacyUnhandledExceptionPolicy enabled="1">

The reason for it:
When an exception is thrown in .NET it starts walking back through the stack looking for exception handlers and when it finds one it then does a second walk back through the stack running finally blocks before running the content of the catch. If it does not find a catch then this second walk never happens thus the finally blocks are never run here which is why a global exception handler will always run finally clauses as the CLR will run them when it finds the catch, NOT when it runs it (which I believe means even if you do a catch/throw your finally blocks will still get run).

The reason the app.config fix works is because for .NET 1.0 and 1.1 the CLR had a global catch in it which would swallow Exceptions before they went unmanaged which would, being a catch of course, trigger the finally blocks to run. Of course there is no way the framework can know enough about said Exception to handle it, take for example a stack overflow, so this is probably the wrong way of doing it.

The next bit is where it gets a bit sticky, and I am making assumptions based off of what the article says here.

If you are in .NET 2.0+ without the legacy exception handling on then your Exception would fall out into the Windows exception handling system (SEH) which seems pretty darn similar to the CLR one, in that it walks back through frames until it fails to find a catch and then calls a series of events called the Unhandled Exception Filter (UEF). This is an event you can subscribe to, but it can only have ONE thing subscribed to it at a time, so when something does subscribe Windows hands it the address of the callback that was there before, allowing you to set up a chain of UEF handlers - BUT THEY DON'T HAVE TO HONOR that address, they should call the address themselves, but if one breaks the chain, bap, you get no more error handling. I assume that this is what is happening when you cancel windows error reporting, it breaks the UEF chain which means that the application is shut down immediately and the finally blocks are not run, however if you let it run to the end and close it, it will call the next UEF in the chain. .NET will have registered one which is what the AppDomain.UnhandledException is called from (thus even this event is not guaranteed) which I assume is also where you get your finally blocks called from - as I can't see how if you never transition back into the CLR a managed finally block can run (the article does not go into this bit.)

心作怪 2024-10-09 12:31:32

我相信这与调试器连接方式的更改有关。

来自。 NET Framework 4 迁移问题文档:

当调试器无法启动或没有应启动的已注册调试器时,您将不再收到通知。

发生的情况是您选择启动调试器,但您取消了它。我相信这属于这一类别,并且应用程序因此而停止。

I believe this has something to do with changes to how the debugger is attached.

From the .NET Framework 4 Migration Issues document:

You are no longer notified when the debugger fails to start, or when there is no registered debugger that should be started.

What happens is that you choose to start the debugger, but you cancel it. I believe this falls under this category and the application just stops because of this.

醉梦枕江山 2024-10-09 12:31:32

在发布和调试中运行这个,在框架 3.5 和 4.0 中,我在所有实例中都看到“最后”,是的,从命令行运行它,甚至关闭了我的 vs 会话,也许它是你的机器上的东西或作为 Kobi指出,可能与平台相关(我使用的是 Win7 x64)

Ran this in both release and debug, in both framework 3.5 and 4.0, I see "in Finally" in all instances, yes running it from command line, went as far as closing my vs sessions, maybe it's something on your machine or as Kobi pointed out, maybe platform related (I'm on Win7 x64)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文