Chr*_*ens 17 .net c# specifications .net-4.5
出现这个问题是因为以前在.NET 4.0中运行的代码在.NET 4.5中出现了未处理的异常,部分原因是try/finallys.如果您需要详细信息,请阅读Microsoft connect.我用它作为这个例子的基础,所以它可能有助于引用.
对于那些选择不阅读这个问题背后细节的人来说,这里可以快速了解发生这种情况的条件:
using(var ms = new MemoryStream(encryptedData))
using(var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
using(var sr = new StreamReader(cryptoStream))
Run Code Online (Sandbox Code Playgroud)
这个问题是从Dispose
CryptoStream方法抛出的异常(因为它们在using语句中,这些异常碰巧是从两个不同的finally块抛出的).当cryptoStream.Dispose()
被召唤时StreamReader
,CryptographicException
被抛出.第二次cryptoStream.Dispose()
调用,在其using语句中,它抛出一个ArgumentNullException
下面的代码从上面提供的链接中删除了大部分不必要的代码,并将using语句展开到try/finallys中,以清楚地表明它们正在抛出finally块.
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");
}
}}
Run Code Online (Sandbox Code Playgroud)
注意:try/finally对应于引用代码中的扩展using语句.
输出:
Propagate, my children Throwing CryptographicExecption Throwing ArgumentException Caught ArgumentException Press any key to continue . . .
似乎没有CryptographicException
执行catch块.但是,删除该catch块会导致异常终止运行时.
编辑:这已更新为规范的最新版本.我碰巧抓住MSDN的那个用了较旧的措辞. Lost
已更新为terminated
.
深入了解C#规范,第8.9.5节和第8.10节讨论了异常行为:
"终止"使得看起来第一个异常将永远被第二个抛出的异常隐藏,尽管它似乎不是正在发生的事情.
在大多数情况下,可以很容易地看到运行时正在做什么.代码执行到第一个finally块(F1
),其中抛出异常.当异常传播时,第二个异常从第二个finally块(F2
)抛出.
根据规范,CryptographicException
抛出F1
的现在终止了,运行时正在寻找一个处理程序ArgumentException
.运行时找到一个处理程序,并在ArgumentException
(C1
)的catch块中执行代码.
这里有雾:规范说第一个例外将被终止.但是,如果C2
从代码中删除了第二个catch块(),那么CryptographicException
它应该丢失,现在是一个未处理的异常,它终止了程序.与C2
存在,该代码将不会从一个未处理的异常终止,所以在表面上它似乎要处理异常,但永远不会执行该块中的实际的异常处理代码.
问题基本相同,但针对特异性进行了重新措辞.
如何CryptographicException
由于ArgumentException
从封闭的finally块抛出异常而终止,因为删除catch (CryptographicException)
块会导致异常无法处理并终止运行时?
由于运行时似乎被处理CryptographicException
时,catch (CryptographicException)
块存在,为什么不执行块内的代码?
我仍然在研究这个问题的实际行为,并且许多答案在至少回答上述问题的部分内容时特别有帮助.
另一个奇怪的行为,就是当你运行带有catch (CryptographicException)
注释掉的块的代码时发生的,是.NET 4.5和.NET 3.5之间的区别..NET 4.5将抛出CryptographicException
并终止应用程序.但是,.NET 3.5似乎表现得更符合C#规范中的异常.
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
在.NET 3.5中,我看到了我在规范中读到的内容.异常变为"丢失"或"终止",因为唯一需要被抓住的是ArgumentException
.因此,程序可以继续执行.我的机器上只有.NET 4.5,我想知道这是否发生在.NET 4.0中?
.NET中的异常处理有3个不同的阶段:
一旦throw语句执行,第1阶段就会启动.CLR正在寻找一个范围内的catch块,它宣告它愿意处理异常.在这个阶段,在C#中,没有代码执行.从技术上讲,可以执行代码,但该功能不会在C#中公开.
找到catch块后,阶段2开始,CLR知道恢复执行的位置.然后,它可以可靠地确定最终需要执行的块.任何方法堆栈帧也都被解开.
一旦所有finally块完成并且堆栈被展开到包含catch语句的方法,则阶段3开始.指令指针设置为catch块中的第一个语句.如果此块不包含进一步的throw语句,则执行将在catch块之后的语句处恢复正常.
因此,代码片段中的核心要求是范围内存在catch(CryptographicException).没有它,第1阶段失败,CLR不知道如何恢复执行.线程已死,通常还会根据异常处理策略终止程序.finally块中没有一个会执行.
如果在阶段2中,finally块抛出异常,则立即中断正常的异常处理序列.最初的异常是"丢失",它永远不会进入第3阶段,因此无法在您的程序中观察到.异常处理从阶段1开始,现在正在寻找新的异常并从该finally块的范围开始.
如果在执行finally块期间抛出异常,并且已经传播了异常,则该异常将丢失
基本上,执行时会发生什么:
CryptographicException
终于被抛入了内心.ArgumentException
.由于"CryptographicException"在这个时间点被"传播",它就会丢失.ArgumentException
捕获.......并且第一个例外简单地消失在以太中是没有意义的,只是因为从另一个finally块抛出了另一个异常.
这正是基于您引用的C#语言规范所发生的情况.第一个异常(CryptographicException
)实际上消失了 - 它"丢失"了.
但是,你只能通过显式使用来达到这种状态finally
,所以我相信你假设你正在考虑这种期望或可能性的错误处理(因为你try
在那时使用,这意味着你已经接受了你可能有例外).
这基本上在规范中有详细解释8.9.5
(8.10
引用的文字引用了本节):
如果finally块抛出另一个异常,则终止当前异常的处理.
第一个例外,在你的情况下ArgumentException
,基本上"消失".
事实证明,我并没有疯。根据我对这个问题的回答,我认为我似乎很难理解规范中明确概述的内容。这确实一点也不难掌握。
事实是,该规范有意义,但行为则不然。当您在较旧的运行时中运行代码时,这种情况尤其明显,它的行为完全不同......或者至少看起来如此。
我在 x64 Win7 机器上看到的:
.NET v2.0-3.5 - 引发时的 WER 对话框CryptographicException
。点击后Close the program
,程序继续,就好像从未抛出异常一样。应用程序并未终止。这是人们在阅读规范时所期望的行为,并且由在 .NET 中实现异常处理的架构师明确定义。
.NET v4.0-4.5 - 不显示 WER 对话框。相反,会出现一个窗口,询问您是否要调试程序。单击no
会使程序立即终止。此后不会执行任何finally 块。
事实证明,几乎任何试图回答我的问题的人都会得到与我完全相同的结果,这解释了为什么没有人能回答我的问题,即为什么运行时会因它吞下的异常而终止。
谁会怀疑即时调试器?
您可能已经注意到,在 .NET 2 下运行应用程序会产生与 .NET 4 不同的错误对话框。但是,如果您像我一样,您会在开发周期中期待该窗口,所以您没有想想吧。
vsjitdebugger 可执行文件强制终止应用程序,而不是让它继续运行。在2.0运行时,dw20.exe
没有这种行为,事实上,你首先看到的是WER消息。
由于 jit 调试器终止了应用程序,它看起来似乎不符合规范的要求,而实际上,它确实符合规范的要求。
为了测试这一点,我通过将注册表项HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Auto
从 1 更改为 0,禁止 vsjitdebugger 在失败时启动。果然,应用程序忽略了异常并继续运行,就像 .NET 2.0 一样。
事实证明,有一个解决方法,尽管实际上没有理由解决此行为,因为您的应用程序正在终止。
Manually choose the debugging engines
并单击您想要调试的“是”。 归档时间: |
|
查看次数: |
1421 次 |
最近记录: |