从 finally 抛出异常时,不会评估 Catch 块 [英] Catch block is not being evaluated when exceptions are thrown from finallys

查看:25
本文介绍了从 finally 抛出异常时,不会评估 Catch 块的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

出现这个问题是因为之前在 .NET 4.0 中运行的代码在 .NET 4.5 中出现未处理的异常而失败,部分原因是 try/finallys.如果您需要详细信息,请参阅 微软连接.我将其用作此示例的基础,因此参考可能会有所帮助.

代码

对于那些选择不阅读这个问题背后的细节的人,下面是发生这种情况的条件的快速浏览:

using(var ms = new MemoryStream(encryptedData))使用(var cryptoStream = new CryptoStream(encryptedData,decryptor,CryptoStreamMode.Read))使用(var sr = new StreamReader(cryptoStream))

这个问题是 CryptoStream 的 Dispose 方法抛出了异常(因为它们在 using 语句中,这些异常恰好是从两个不同的 finally 块抛出的).当 cryptoStream.Dispose()StreamReader 调用时,会抛出 CryptographicException.第二次 cryptoStream.Dispose() 被调用,在它的 using 语句中,它抛出一个 ArgumentNullException

以下代码从上面提供的链接中删除了大部分不必要的代码,并将 using 语句展开到 try/finallys 中,以清楚地表明它们正被扔到 finally 块中.

使用系统;使用 System.Security.Cryptography;命名空间沙盒{公开课计划{public static void Main(string[] args){尝试{尝试{尝试{Console.WriteLine(传播,我的孩子们");}最后{//F1Console.WriteLine(抛出加密执行");抛出新的 CryptographicException();}}最后{//F2Console.WriteLine(抛出参数异常");抛出新的 ArgumentException();}}捕获(参数异常){//C1Console.WriteLine("Caught ArgumentException");}//如果这是在封闭的 try/catch 中,则行为相同捕获(加密异常){//C2Console.WriteLine(Caught CryptographicException");}Console.WriteLine(走出异常雷区");}}}

注意:try/finally 对应于引用代码中的扩展 using 语句.

输出:

<前>传播,我的孩子们抛出 CryptographicExecption抛出参数异常捕获参数异常按任意键继续 ...

CryptographicException catch 块似乎从未被执行过.但是,删除该 catch 块会导致异常终止运行时.

更多信息

这已更新为规范的最新版本.我碰巧从 MSDN 上抢到的那个有较旧的措辞.Lost 已更新为 terminated.

深入了解 C# 规范,第 8.9.5 和 8.10 节讨论异常行为:

  • 当抛出异常时,包括从 finally 块内部抛出的异常,控制将转移到封闭的 try 语句中的第一个 catch 子句.这会继续尝试语句,直到找到合适的语句为止.
  • 如果在 finally 块的执行过程中抛出异常,并且异常已经被传播,该异常被终止

终止"看起来第一个异常将永远被第二个抛出的异常隐藏,尽管它似乎不是正在发生的事情.

我确定问题就在这里

在大多数情况下,很容易形象化运行时正在做什么.代码执行到第一个 finally 块 (F1),在那里抛出异常.随着异常的传播,第二个异常从第二个 finally 块 (F2) 中抛出.

根据规范,从 F1 抛出的 CryptographicException 现在已终止,并且运行时正在寻找 ArgumentException 的处理程序.运行时找到一个处理程序,并为 ArgumentException (C1) 执行 catch 块中的代码.

这里是模糊的地方:规范说第一个异常将被终止.但是,如果从代码中删除第二个 catch 块 (C2),则本应丢失的 CryptographicException 现在是终止程序的未处理异常.在 C2 存在的情况下,代码不会因未处理的异常而终止,因此从表面上看它似乎正在处理异常,但实际上是块中的异常处理代码永远不会被执行.

问题

问题基本相同,但为了具体而重新措辞.

  1. CryptographicException 如何由于从封闭的 finally 块中抛出的 ArgumentException 异常而终止,因为删除了 catch (CryptographicException) 块导致异常未被处理并终止运行时?

  2. 既然当存在 catch (CryptographicException) 块时运行时似乎正在处理 CryptographicException,为什么块内的代码没有执行?


额外信息编辑

我仍在研究此问题的实际行为,许多答案至少对回答上述问题的一部分特别有帮助.

另一种奇怪的行为,当您运行带有注释掉的 catch (CryptographicException) 块的代码时,会发生 .NET 4.5 和 .NET 3.5 之间的差异..NET 4.5 将抛出 CryptographicException 并终止应用程序.然而,.NET 3.5 的行为似乎更符合 C# 规范所在的异常.

<前>传播,我的孩子们抛出 CryptographicExecption未处理的异常:System.Security.Cryptography.CryptographicException [...]ram.cs:第 23 行抛出参数异常捕获参数异常走出异常雷区

在 .NET 3.5 中,我看到了我在规范中读到的内容.异常变得丢失"或终止",因为唯一需要被捕获的是ArgumentException.因此,程序可以继续执行.我的机器上只有 .NET 4.5,不知道 .NET 4.0 是否会发生这种情况?

解决方案

事实证明,我并不疯了.根据我对这个问题的回答,我认为我似乎很难理解规范中如此清晰地概述的内容.真的一点都不难掌握.

事实是,规范是有道理的,而行为则不然.当您在较旧的运行时中运行代码时,情况更是如此,它的行为完全...或者至少看起来与此不同.

快速回顾

我在 x64 Win7 机器上看到的:

事实证明,几乎任何试图回答我的问题的人都会得到与我完全相同的结果,所以这解释了为什么没有人能够回答我的问题,即运行时为什么会因它吞下的异常而终止.

它从来都不是你所期望的

谁会怀疑即时调试器?

您可能已经注意到,在 .NET 2 下运行应用程序会产生与 .NET 4 不同的错误对话框.但是,如果您像我一样,在开发周期中会期望出现该窗口,因此您没想到.

vsjitdebugger 可执行文件强行终止应用程序,而不是让它继续运行.在 2.0 运行时,dw20.exe 没有这个行为,事实上,你看到的第一件事就是 WER 消息.

多亏了 jit 调试器终止了应用程序,它使它看起来不符合规范,但实际上它确实符合规范.

为了测试这一点,我通过将 HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionAeDebugAuto 中的注册表项从 1 更改为 0 来禁用 vsjitdebugger 在失败时启动.果然,应用程序忽略异常并继续,就像 .NET 2.0 一样.

<小时>

事实证明,有一种解决方法,但实际上没有理由解决此行为,因为您的应用程序正在终止.

  1. 当 Just-In-Time 调试器窗口弹出时,勾选手动选择调试引擎,然后点击是,您要调试.
  2. 当 Visual Studio 为您提供引擎选项时,单击取消.
  3. 这将导致程序继续运行,或弹出 WER 对话框,具体取决于您的机器配置.如果发生这种情况,告诉它关闭程序实际上并不会关闭它,它会继续运行,好像一切正​​常.

This question came about because code that worked previously in .NET 4.0 failed with an unhandled exception in .NET 4.5, partly because of try/finallys. If you want details, read more at Microsoft connect. I used it as the base for this example, so it might be helpful to reference.

The code

For the people who chose to not read about the details behind this question, here is a very quick look at the conditions where this happened:

using(var ms = new MemoryStream(encryptedData))
using(var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
using(var sr = new StreamReader(cryptoStream))

This issue is that there are exceptions thrown from the Dispose method of CryptoStream (since they are inside a using statement, these exceptions happen to be thrown from two different finally blocks). When cryptoStream.Dispose() is called by the StreamReader, the CryptographicException is thrown. The second time cryptoStream.Dispose() is called, in its using statement, it throws a ArgumentNullException

The following code removes most of the unnecessary code from the link provided above, and unwinds the using statements into try/finallys to clearly show that they are being throw in finally blocks.

using System;
using System.Security.Cryptography;
namespace Sandbox
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                try
                {
                    try
                    {
                        Console.WriteLine("Propagate, my children");
                    }
                    finally
                    {
                        // F1
                        Console.WriteLine("Throwing CryptographicExecption");
                        throw new CryptographicException();
                    }
                }
                finally
                {
                    // F2
                    Console.WriteLine("Throwing ArgumentException");
                    throw new ArgumentException();
                }
            }
            catch (ArgumentException)
            {
                // C1
                Console.WriteLine("Caught ArgumentException");
            }
            // Same behavior if this was in an enclosing try/catch
            catch (CryptographicException)
            {
                // C2
                Console.WriteLine("Caught CryptographicException");
            }
            
            Console.WriteLine("Made it out of the exception minefield");
        }
    }}

Note: The try/finally correspond to expanded using statements from the referenced code.

Output:

    Propagate, my children
    Throwing CryptographicExecption
    Throwing ArgumentException
    Caught ArgumentException
    Press any key to continue . . .

It doesn't appear that the CryptographicException catch block is ever executed. However, removing that catch block causes the exception to terminate the runtime.

A little more information

EDIT: This was updated to the newest revision of the specification. The one I happened to grab off of MSDN had older wording. Lost has been updated to terminated.

Diving into the C# spec, sections 8.9.5 and 8.10 discuss exception behavior:

  • When an exception is thrown, including from inside a finally block, control is transferred to the first catch clause in an enclosing try statement. This continues up try statements until a suitable one is found.
  • If an exception is thrown during execution of a finally block, and an exception was already being propagated, that exception is terminated

"Terminated" makes it seem that the first exception would forever be hidden by the second thrown exception, though it doesn't seem to be what is happening.

I'm sure the question is in here somewhere

For the most part, it's easy to visualize what the runtime is doing. The code executes to the first finally block (F1) where an exception is thrown. As the exception propagates, the second exception is thrown from the second finally block (F2).

According to the spec, the CryptographicException thrown from F1 is now terminated, and the runtime is looking for a handler for the ArgumentException. The runtime finds a handler, and executes the code in the catch block for the ArgumentException (C1).

Here is where it gets foggy: the spec says that the first exception would be terminated. However, if the second catch block (C2) is removed from the code, the CryptographicException that was supposedly lost, is now an unhandled exception that terminates the program. With the C2 present, the code will not terminate from an unhandled exception, so on the surface it appears to be handling the exception, but the actually exception handling code in the block is never executed.

Questions

The questions are basically the same, but re-worded for specificity.

  1. How is it that the CryptographicException becomes terminated due to the ArgumentException exception thrown from the enclosing finally block, as removing the catch (CryptographicException) block causes the exception to go unhandled and terminate the runtime?

  2. Since the runtime seems to be handling the CryptographicException when the catch (CryptographicException) block is present, why is the code inside of the block not executing?


Extra informational Edit

I'm still looking into the actual behavior of this, and many of the answers have been particularly helpful in at least answering parts of the above questions.

Another curious behavior, that happens when you run the code with the catch (CryptographicException) block commented out, is the difference between .NET 4.5 and .NET 3.5. .NET 4.5 will throw the CryptographicException and terminate the application. .NET 3.5, however, seems to behave more according to the C# spec where the exception.

Propagate, my children
Throwing CryptographicExecption

Unhandled Exception: System.Security.Cryptography.CryptographicException [...]
ram.cs:line 23
Throwing ArgumentException
Caught ArgumentException
Made it out of the exception minefield

In .NET 3.5, I see what I read in the spec. The exception becomes "lost", or "terminated", since the only thing that ever needs to get caught is the ArgumentException. Because of that the program can continue execution. I only have .NET 4.5 on my machine, I wonder if this happens in .NET 4.0?

解决方案

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_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionAeDebugAuto 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.

这篇关于从 finally 抛出异常时,不会评估 Catch 块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆