C# 可以解释PrepareConstrainedRegions和Thread.Abort的这种意外行为吗?

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 =

今晚我在玩游戏,以便更好地完善我对细节的理解。我以前偶尔使用过它们,但在这些情况下,我大多严格遵循既定的模式。无论如何,我注意到了一些我无法解释的奇怪的事情

考虑以下代码。注意,我以.NET4.5为目标,并使用未连接调试器的发布版本对其进行了测试

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=False
  • 在猜之前,请阅读文档。我包括以下相关章节

    约束执行区域(CER)是 编写可靠的托管代码。CER定义了一个区域,其中 公共语言运行库(CLR)受到限制,不能抛出带外 会阻止区域中的代码在中执行的异常 它的全部。在该区域内,用户代码从 执行可能导致带外抛出的代码 例外情况必须立即使用PrepareConstrainedRegions方法 在try块之前,将catch、finally和fault块标记为 受约束的执行区域。一旦标记为受约束区域, 代码只能调用具有强可靠性契约的其他代码,并且 代码不应分配或虚拟调用未准备好的或 不可靠的方法,除非代码准备好处理故障最新版本 CLR延迟正在CER中执行的代码的线程中止。

    try/catch/finally是一种异常处理机制 具有与非托管系统相同级别的可预测性保证 版本catch/finally块是CER。块中的方法 需要提前准备,并且必须是不可中断的

    我现在特别关心的是防止线程中止。有两种:一种是通过
    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;