C#的强迫执行域Constrained Execution Regions(CERs)
Contents
强迫执行域(CERs)通常用于遇到未预见的异常时,保证系统被多个AppDomain或者进程共享的状态的正确性。
这种异常我们通常称之为Asynchronous Exception。比如当调用一个函数时,CLR需要去加载assembly,在AppDomain的堆上创建类型,调用类型的类构造函数,JIT把IL转换成native代码等等。当这些过程出错时,CLR会抛异常。如果这个异常是在代码的catch或者finally抛出的话,catch和finally中的错误恢复代码就不能被执行了,这样系统的状态就有可能会出错。
考虑如下的代码示例:
1 | sealed class Type1 |
第9行和第18行使用了CER,需要using System.Runtime.CompilerServices; 和using System.Runtime.ConstrainedExecution;。当JIT编译器看到CERs时,会先编译catch和finally中的代码。JIT会加载需要的assembly,创建对象,调用类构造函数,JIT所有的方法。如果任何一步出了异常,那么这个异常会在try代码块之前抛出,从而保证了系统的状态始终是正确的。
运行上面的代码,可以得到如下的输出:
1 | Type1's static ctor called |
如果注释掉第9行和第18行,得到的输出如下:
1 | In try |
补充几个关于Exception的小知识:
- 在watch窗口输入$exception,可以看到CLR的exception信息。对于C++,可以在watch窗口输入@err,hr,相当于显示上一次调用API后再调用GetLastError。
- 输入@eax,hr显示eax寄存器的值,由于win的API的返回值放在eax中,所以这句话的意思就是的到最近一个API的返回值。
- 如果你需要看的是一个array,你可以通过在watch中写 array, 3来看数组的前3个值。
- 可以监听AppDomain的FirstChanceException事件来找到exception的第一现场。
- 如果想先catch在throw。应该用
catch(e){throw;}
而不是catch(e){throw e;}
。 - 在函数前加属性
[MethodImpl](MethodImplOptions.NoInlining)]
可以强迫JIT在异常抛出时不要inline这个方法。我在之前的博客中介绍过如何使用debugger attribute来定制在Visual Studio中的信息。参见定制自己的Visual Studio的Debugger Visualizer和定制C#在Visual Studio中的debug信息 - 可以调用
SetErrorMode(SEM_NOGPFAULTERRORBOX)
来禁用Windows Error Reporting,就是出错后问你要不要发送出错信息的那个对话框。