考虑以下代码。请注意,我针对.NET 4.5,并使用Release构建进行了测试,并未附加调试器。
public class Program
{
public static void Main(string[] args)
{
bool toggle = false;
bool didfinally = false;
var thread = new Thread(
() =>
{
Console.WriteLine("running");
RuntimeHelpers.PrepareConstrainedRegions();
try
{
while (true)
{
toggle = !toggle;
}
}
finally
{
didfinally = true;
}
});
thread.Start();
Console.WriteLine("sleeping");
Thread.Sleep(1000);
Console.WriteLine("aborting");
thread.Abort();
Console.WriteLine("aborted");
thread.Join();
Console.WriteLine("joined");
Console.WriteLine("didfinally=" + didfinally);
Console.Read();
}
}
你认为这个程序的输出会是什么?
- didfinally=True
- didfinally=False
在猜测之前,请阅读文档。下面是相关部分。
受限执行区域(CER)是编写可靠托管代码机制的一部分。 CER定义了一个区域,在该区域中,公共语言运行时(CLR)被限制抛出超出范围的异常,该异常将阻止代码在该区域内完全执行。在该区域内,用户代码不能执行会导致超出范围异常的代码。 PrepareConstrainedRegions方法必须紧接在try块之前,并将catch、finally和fault块标记为受限执行区域。一旦标记为受限区域,代码只能调用具有强可靠性契约的其他代码,并且除非代码准备好处理失败,否则代码不应分配或进行虚拟调用到未准备或不可靠的方法。 CLR延迟执行在CER中执行的代码的线程中止操作。
和
可靠性的try/catch/finally是一种异常处理机制,具有与非托管版本相同级别的可预测性保证。catch/finally块是CER。块中的方法需要提前准备并且必须是不可中断的。我现在特别关注防止线程中止。有两种情况:通过Thread.Abort进行的普通类型和CLR主机可以对您进行强制中止的类型。finally块已经在某种程度上受到了Thread.Abort的保护。然后,如果您将该finally块声明为CER,则还会获得来自CLR主机中止的额外保护...至少我认为这就是理论。
所以基于我所知道的,我猜#1。它应该打印didfinally=True。ThreadAbortException在代码仍在try块中时被注入,然后CLR允许finally块按预期运行,即使没有CER,对吗?
好的,这不是我得到的结果。我得到了一个完全意外的结果。对我来说,既没有发生#1也没有发生#2。相反,我的程序在Thread.Abort
处挂起。以下是我观察到的情况。
PrepareConstrainedRegions
的存在会延迟try
块内的线程中止。PrepareConstrainedRegions
的缺失允许在try
块中中止。
所以百万美元的问题是为什么?文档中没有提到这种行为。事实上,我正在阅读的大部分内容实际上都建议将关键的不间断代码放在finally
块中,特别是为了防止线程中止。
也许,PrepareConstrainedRegions
除了在finally
块中之外,还会延迟try
块中的正常中止。但是CLR主机中止只能在CER的finally
块中延迟吗?有人能提供更多关于此问题的澄清吗?
try
块不是CER的一部分。那么,为什么我们应该期望带外异常在try
块执行时被延迟呢?通常,在PrepareConstrainedRegions
之后立即看到空的try
块。然而,我喜欢你关于这种情况下“未定义行为”的观点。我肯定可以接受这个理由。我打算接受这个答案。 - Brian Gideon