我今晚正在玩Constrained Execution Regions以更好地理解我对细节的理解。我之前曾经使用过它们,但在这些情况下,我大多坚持已建立的模式。无论如何,我注意到一些我无法解释的奇特。
请考虑以下代码。注意,我的目标是.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();
}
}
您认为该计划的输出会是什么?
在你猜之前阅读文档。我在下面列出了相关部分。
约束执行区域(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
除了try
块之外,还会延迟finally
块中的正常中止。但是CLR主机中止只会在CER的finally
块中延迟?任何人都可以提供更清晰的信息吗?
答案 0 :(得分:2)
我认为我至少有一个关于发生了什么的理论。如果更改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
内的代码更安全一些。中止被延迟,直到达到数据结构更可能处于一致状态的点。当然,这假设开发人员在更新关键数据结构的过程中没有故意(或无意地)将线程置于可警告状态。
所以基本上try
在catch
内部注入中止时会增加进一步约束的未记录功能。由于未记录此功能,因此开发人员应谨慎避免依赖此假设,方法是不将关键代码放入CER构造的finally
块中。正如文档所述,fault
,{{1}}和{{1}}(不在C#中)块正式定义为CER的范围。
答案 1 :(得分:2)
[来自评论]
我将我的答案分为两部分:CER和处理ThreadAbortException。
我不相信CER的目的是帮助线程中止;这些不是你正在寻找的机器人。我可能会误解问题的陈述,这些东西往往变得相当沉重,但我发现这些短语在文档中是关键的(诚然,其中一个实际上是在另一个部分比我提到的那样):
The code cannot cause an out-of-band exception
和
user code creates non-interruptible regions with a reliable try/catch/finally that *contains an empty try/catch block* preceded by a PrepareConstrainedRegions method call
尽管没有受到约束代码的直接启发,但线程中止是一个带外异常。约束区域仅保证一旦最终执行,只要它遵守它所承诺的约束,它就不会因为托管运行时操作而中断,否则不会中断非托管最终块。线程中止中断非托管代码,就像它们中断托管代码一样,但没有约束区域 某些保证,并且可能也是您可能正在寻找的行为的不同推荐模式。我怀疑这主要是作为阻止垃圾收集的线程暂停的障碍(可能通过在区域的持续时间内将线程切换到抢占式垃圾收集模式,如果我不得不猜测的话)。我可以想象将它与弱引用,等待句柄和其他低级管理例程结合使用。
至于出乎意料的行为,我的想法是你没有通过声明约束区域来达到你所承诺的合同,因此结果没有记录,应该被认为是不可预测的。在尝试中延迟线程中止似乎很奇怪,但我认为这是非预期用法的副作用,这只是值得进一步探索对运行时的学术理解(一类易变的知识,由于无法保证行为,未来的更新可能会改变此行为。)
现在,我不确定在非预期的方式使用上述副作用的程度是多少,但是如果我们退出使用力来影响我们的控制体并让事情运行的背景他们通常的方式,我们得到一些保证:
有了这个,这里有一个意味着的技术样本,用于需要中止弹性的情况。我在一个样本中混合了多种技术,这些技术不需要同时使用(通常你不会)根据你的需要为你提供一些选项。
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;
}
我没有关于CER的专家,所以如果我误解了,请告诉我。我希望这会有所帮助:)
答案 2 :(得分:1)
您的意外行为是由于您的代码具有最高的可靠性。
定义以下方法:
private static bool SwitchToggle(bool toggle) => !toggle;
[ReliabilityContract(Consistency.WillNotCorruptState,Cer.Success)]
private static bool SafeSwitchToggle(bool toggle) => !toggle;
并使用它们而不是while循环的主体。您会注意到,当调用SwitchToggle时,该循环变为可终止,而当调用SafeSwitchToggle时,该循环不再可终止。
如果在try块中添加不具有Consistency.WillNotCorruptState或Consistency.MayCorruptInstance的其他任何方法,情况也是如此。