2013-08-29 90 views
4

我今晚在玩Constrained Execution Regions,以更好地理解我对细节的理解。我之前曾使用过它们,但在那些情况下,我大多数都严格遵守既定模式。无论如何,我注意到了一些我不能解释的奇特的东西。可以解释PrepareConstrainedRegions和Thread.Abort的这种意外行为吗?

考虑下面的代码。请注意,我的目标是.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(); 
    } 
} 

你认为这个程序的输出是什么?

  1. didfinally =真
  2. didfinally =假

之前你猜阅读文档。我在下面列出了相关部分。

约束执行区域(CER)是编写可靠托管代码的机制的一部分。甲CER限定其中 公共语言运行库(CLR)从抛出带外 例外将防止在该地区的代码从 其全部执行限制的区域。内的区域中,用户代码从 执行代码将导致出带外 例外的投掷限制。 的PrepareConstrainedRegions方法必须立即 先于try块和标记赶上,最后,与故障块作为 约束的执行区域。一旦标记为约束区域, 代码只能调用具有强可靠性合同的其他代码,并且 代码不应分配或虚拟调用不可靠的方法或不可靠的方法,除非代码准备处理失败。 CLR延迟在CER中执行的代码的线程中止。

可靠性的try/catch /最后是一个异常处理机制 与同级别的可预测性担保作为非托管 版本。 catch/finally块是CER。块 中的方法需要提前准备,并且必须是不可中断的。

我特别关注现在的问题是防范线程中止。有两种:通过Thread.Abort进行的正常变化,然后是CLR主机可以对你全部中世纪进行强制中止的那种。 finally块在某种程度上已经被保护而不受Thread.Abort的影响。然后,如果您将finally块声明为CER,那么您可以从CLR主机中止获得额外保护,至少我认为这是理论。

因此,基于我在想什么,我知道我猜#1。它应该打印didfinally = True。当代码仍在try块中时,ThreadAbortException被注入,然后CLR允许finally块按预期运行,即使没有CER权限也是如此?

嗯,这不是我得到的结果。我得到了一个完全意外的结果。 #1或#2都不适合我。相反,我的程序挂在Thread.Abort。这是我观察到的。

  • PrepareConstrainedRegions的存在延迟线程在try块内部中止。
  • PrepareConstrainedRegions的缺席允许他们在try块。

那么百万美元的问题是为什么呢?文档没有提到我能看到的任何地方的这种行为。实际上,我正在阅读的大部分内容实际上都是暗示您在finally区块中放置了不可中断的关键代码,以防止线程中止。

也许,PrepareConstrainedRegions延迟正常中止在除了finallytry块。但是CLR主机异常终止只在CER的finally块中延迟?任何人都可以提供更多的清晰度?

回答

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

尽管未在受约束代码直接激发,线程中止超出带外异常。受约束的区域只能保证一旦finally被执行,只要它服从它所承诺的约束,它就不会被中断用于托管的运行时操作,否则它们不会中断非托管的finally块。线程中止会中断非托管代码,就像它们中断托管代码一样,但没有约束区域的一些保证,也可能是您可能要查找的行为的不同推荐模式。我怀疑这主要是作为阻止垃圾收集的线程挂起的屏障(如果必须猜测,可能通过在该区域持续时间内将线程切换为抢先式垃圾收集模式)。我可以想象使用这个结合弱引用,等待句柄和其他低级管理例程。

至于意外的行为,我的想法是,你没有达到你所声明的约束区所承诺的合同,所以结果没有记录,应该被认为是不可预测的。看起来很奇怪,线程放弃会在尝试中被延迟,但我相信这是非预期用法的一个副作用,这仅仅是值得进一步探索对于运行时的学术理解(一类是易变的知识,因为没有行为保证,未来的更新可能会改变这种行为)。

现在,我不确定在以无意的方式使用上述副作用的程度如何,但如果我们退出使用力量来影响我们的控制体并让事情顺其自然的背景下他们通常会,我们也得到一些保证:

  • 一个Thread.ResetAbort可以,在某些情况下,防止螺纹
  • ThreadAbortExceptions可以被捕获的流产;整个catch块将运行,并且,如果中止未被重置,ThreadAbortException将在退出catch块时自动重新生成。
  • 所有finally块都保证在ThreadAbortException解除调用堆栈的同时运行。

就这样,这里是技术意味着到在中止弹性是必要的情况下可以使用的样本。我在单个样本中混合了多种技术,这些技术不需要同时使用(通常不会),只是根据您的需要为您提供一些选项。

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的专家,所以任何人请让我知道,如果我误解了。我希望这会有所帮助:)

+1

你说得很好。我认为我的大部分困惑都来自文档,暗示'try'块不是CER的一部分。那么为什么我们应该期待带外的异常在执行'try'块时被延迟。通常你会在'PrepareConstrainedRegions'之后立即看到'try'块。不过,在这种情况下,我喜欢你关于“未定义的行为”的观点。我绝对可以接受这个理由。我将继续并接受答案。 –

+0

是的,我实际上认为我明白发生了两次事情,然后才意识到整个情况都在描述的背景之外。 – TheXenocide

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内部注入异常时会发生异常终止。由于没有记录此功能,开发人员谨慎的做法是不要在CER构造的try块中放置关键代码,以避免依赖此假设。正如文档中所记录的,只有catch,finallyfault(不在C#中)块被正式定义为CER的范围。

+0

我觉得CERs上的MSDN文档中的“约束”部分充分说明了什么是和没有处理:http://msdn.microsoft.com/en-us/library/ms228973。 asX – TheXenocide

+0

@TheXenocide:如果我正确地阅读它,它会告诉您(程序员)在CER中无法完成的事情。我不认为它告诉你一个'try'块的行为(这在技术上不是CER的一部分)。我错过了什么? –

+0

我的答案开始变得漫长而且涉及,所以我只是添加了一个真实的答案。 – TheXenocide

相关问题