Catch block is not being evaluated when exceptions are thrown from finallys

假如想象 提交于 2019-12-03 10:12:00

Exception processing in .NET has 3 distinct stages:

  • stage 1 kicks in gear as soon as a throw statement executes. The CLR goes looking for a catch block that's in scope that advertizes that it is willing to handle the exception. At this stage, in C#, no code executes. Technically it is possible to execute code but that capability is not exposed in C#.

  • stage 2 starts once the catch block is located and the CLR knows where execution resumes. It can then reliably determine what finally blocks need to be executed. Any method stack frames are unwound as well.

  • stage 3 starts once all finally blocks are completed and the stack is unwound to the method that contains the catch statement. The instruction pointer is set to the first statement in the catch block. If this block contains no futher throw statements, execution resumes as normal at the statement past the catch block.

So a core requirement in your code snippet is that there is a catch (CryptographicException) in scope. Without it, stage 1 fails and the CLR doesn't know how to resume execution. The thread is dead, usually also terminating the program depending on exception handling policy. None of the finally blocks will execute.

If in stage 2 a finally block throws an exception then the normal exception handling sequence is immediately interrupted. The original exception is "lost", it never gets to stage 3 so cannot be observed in your program. Exception handling starts back at stage 1, now looking for the new exception and starting at the scope of that finally block.

If an exception is thrown during execution of a finally block, and an exception was already being propagated, that exception is lost

Basically, what's happening when you execute:

  • CryptographicException is thrown in inner finally.
  • Outer-scope finally executes, and throws ArgumentException. Since "CryptographicException" was "being propogated" at this point in time, it is lost.
  • Final catches occur, and ArgumentException is caught.

... and it wouldn't make sense for the first exception to simply disappear into the ether, just because there was another exception thrown from a different finally block.

This is exactly what happens, based on the C# language specification you quoted. The first exception (CryptographicException) effectively disappears - it's "lost".

You can only reach this state by explicitly using finally, though, so I believe the assumption is that you're providing the error handling with this expectation or possibility in mind (as you're using try at that point, which means you've accepted you may have an exception).

This is basically explained in detail in the spec in 8.9.5 (the text in 8.10 you quoted refers to this section):

If the finally block throws another exception, processing of the current exception is terminated.

The first exception, in your case the ArgumentException, basically "disappears".

As it turns out, I am not crazy. Based on the answers I got to this question, I think it seemed like I was having difficulty understanding what is so clearly outlined in the spec. It's really not at all difficult to grasp.

The truth is that the spec makes sense, while the behavior wasn't. This is seen even more so when you run the code in an older runtime, where it behaves completely different...or at least appears to.

A quick recap

What I saw, on my x64 Win7 machine:

  • .NET v2.0-3.5 - WER dialog when the CryptographicException is thrown. After hitting Close the program, the program continues, as if the execption were never thrown. The application is not terminated. This is the behavior one would expect from reading the spec, and is well defined by the architects who implemented exception handling in .NET.

  • .NET v4.0-4.5 - No WER dialog is displayed. Instead, a window appears asking if you want to debug the program. Clicking no causes the program to terminate immediately. No finally blocks are executed after that.

As it turns out, pretty much anybody who would try to answer my question would get the exact same results as I did, so that explains why nobody could answer my question of why the runtime was terminating from an exception that it swallowed.

It's never quite what you expect

Who would have suspected the Just-In-Time debugger?

You may have noticed that running the application under .NET 2 produces a different error dialog than .NET 4. However, if you're like me, you've come to expect that window during the development cycle, and so you didn't think anything of it.

The vsjitdebugger executable was forcibly terminating the application, instead of letting it continue. In the 2.0 runtime, dw20.exe doesn't have this behavior, in fact, the first thing you see is that WER message.

Thanks to the jit debugger terminating the application, it made it seem like it wasn't conforming to what spec says when, in fact, it does.

To test this, I disabled the vsjitdebugger from launching on failure, by changing the registry key at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto from 1 to 0. Sure enough, the application ignored the exception and continued on, just like .NET 2.0.


As it turns out, there is a workaround, though there's really no reason to workaround this behavior, since your application is terminating.

  1. When the Just-In-Time debugger window pops up, check Manually choose the debugging engines and click yes, that you want to debug.
  2. When Visual Studio gives you engine options, click cancel.
  3. This will cause the program to continue, or a WER dialog to pop up, depending on your machine configuration. If that happens, telling it to close the program won't actually close it, it will continue running as if everything was okay.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!