C# 可以解释PrepareConstrainedRegions和Thread.Abort的这种意外行为吗?
今晚我在玩游戏,以便更好地完善我对细节的理解。我以前偶尔使用过它们,但在这些情况下,我大多严格遵循既定的模式。无论如何,我注意到了一些我无法解释的奇怪的事情 考虑以下代码。注意,我以.NET4.5为目标,并使用未连接调试器的发布版本对其进行了测试C# 可以解释PrepareConstrainedRegions和Thread.Abort的这种意外行为吗?,c#,.net,multithreading,threadabortexception,cer,C#,.net,Multithreading,Threadabortexception,Cer,今晚我在玩游戏,以便更好地完善我对细节的理解。我以前偶尔使用过它们,但在这些情况下,我大多严格遵循既定的模式。无论如何,我注意到了一些我无法解释的奇怪的事情 考虑以下代码。注意,我以.NET4.5为目标,并使用未连接调试器的发布版本对其进行了测试 public class Program { public static void Main(string[] args) { bool toggle = false; bool didfinally =
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();
}
}
你认为这个程序的输出会是什么
Thread.Abort执行的常规类型,另一种是CLR主机可以在您身上执行强制中止<代码>最后
块已经受到了线程的保护。在某种程度上中止。然后,如果您将最终
块声明为CER,那么您还可以从CLR主机中止中获得额外的保护…至少我认为这是理论
因此,根据我认为我知道的情况,我猜#1。它应该打印didfinally=True。当代码仍在try
块中时,注入ThreadAbortException
,然后CLR允许finally
块按预期运行,即使没有CER权限
这不是我得到的结果。我得到了一个完全出乎意料的结果。对我来说,一号或二号都没有发生。相反,我的程序挂起在Thread.Abort
。以下是我观察到的情况
的存在延迟了PrepareConstrainedRegions
块内的线程中止try
- 缺少
允许它们进入PrepareConstrainedRegions
块try
finally
块中,专门用于防止线程中止
也许,
PrepareConstrainedRegions
除了finally
块之外,还会延迟try
块中的正常中止。但是CLR主机中止仅在CER的finally
块中延迟?有人能更清楚地说明这一点吗?我想我至少对正在发生的事情有一个理论。如果将while
循环更改为使线程进入可警报状态,则即使使用CER设置,也会注入ThreadAbortException
RuntimeHelpers.PrepareConstrainedRegions();
try
{
// Standard abort injections are delayed here.
Thread.Sleep(1000); // ThreadAbortException can be injected here.
// Standard abort injections are delayed here.
}
finally
{
// CER code goes here.
// Most abort injections are delayed including those forced by the CLR host.
}
因此,PrepareConstrainedRegions
将降级从Thread.Abort
发出的中止,同时在try
块内执行,使其行为更像Thread.Interrupt
。应该很容易理解为什么这会使try
中的代码更加安全。中止被延迟,直到达到数据结构更可能处于一致状态的点。当然,这假设开发人员在更新关键数据结构的过程中无意(或无意地)将线程置于警报状态。
因此,基本上,
PrepareConstrainedRegions
增加了一个未记录的特性,即进一步限制在try
中插入中止的时间。由于此功能未被记录,开发人员应谨慎避免依赖此假设,不将关键代码放在CER构造的try
块中。如文件所述,只有catch
、finally
和fault
(不在C#中)块被正式定义为CER的范围。[Cont'd from comments]
我将把我的答案分成两部分:CER和处理ThreadAbortException
我不相信CER最初是用来帮助线程中止的;这些不是你要找的机器人。我可能也误解了问题的陈述,这些东西往往会变得很重,但我发现在文档中很关键的短语(无可否认,其中一个实际上与我提到的部分不同)是:
该代码不能导致带外异常
及
用户代码使用可靠的try/catch/finally创建不可中断的区域,该区域*包含一个空的try/catch块*前面有一个PrepareConstrainedRegions方法调用
尽管在受约束的代码中没有直接受到启发,但线程中止是带外异常。受约束区域仅保证
bool shouldRun = true;
object someDataForAnalysis = null;
try {
while (shouldRun) {
begin:
int step = 0;
try {
Interlocked.Increment(ref step);
step1:
someDataForAnalysis = null;
Console.WriteLine("test");
Interlocked.Increment(ref step);
step2:
// this does not *guarantee* that a ThreadAbortException will not be thrown,
// but it at least provides a hint to the host, which may defer abortion or
// terminate the AppDomain instead of just the thread (or whatever else it wants)
Thread.BeginCriticalRegion();
try {
// allocate unmanaged memory
// call unmanaged function on memory
// collect results
someDataForAnalysis = new object();
} finally {
// deallocate unmanaged memory
Thread.EndCriticalRegion();
}
Interlocked.Increment(ref step);
step3:
// perform analysis
Console.WriteLine(someDataForAnalysis.ToString());
} catch (ThreadAbortException) {
// not as easy to do correctly; a little bit messy; use of the cursed GOTO (AAAHHHHHHH!!!! ;p)
Thread.ResetAbort();
// this is optional, but generally you should prefer to exit the thread cleanly after finishing
// the work that was essential to avoid interuption. The code trying to abort this thread may be
// trying to join it, awaiting its completion, which will block forever if this thread doesn't exit
shouldRun = false;
switch (step) {
case 1:
goto step1;
break;
case 2:
goto step2;
break;
case 3:
goto step3;
break;
default:
goto begin;
break;
}
}
}
} catch (ThreadAbortException ex) {
// preferable approach when operations are repeatable, although to some extent, if the
// operations aren't volatile, you should not forcibly continue indefinite execution
// on a thread requested to be aborted; generally this approach should only be used for
// necessarily atomic operations.
Thread.ResetAbort();
goto begin;
}
private static bool SwitchToggle(bool toggle) => !toggle;
[ReliabilityContract(Consistency.WillNotCorruptState,Cer.Success)]
private static bool SafeSwitchToggle(bool toggle) => !toggle;