标签:
面向对象编程,编译器功能,CLR功能以及庞大的类库——使.Net Framework成为一个颇具吸引力的开发平台。但所有的这些东西,都会在你的代码中引入你没有什么控制权的“错误点”,如果 OutOfMemoryExcepton等。程序开发不可能对这些异常进行一一捕捉,让应用程序变得绝对健壮。意料意外的异常往往造成程序状态的破坏,为 了缓解对状态的破坏,可以做下面几件事:
●执行catch或finally块时,CLR不允许终止线程,所以可以向下面这样写是Transfer方法变得健壮:
private void Transfer(Account from, Account to, decimal amount) { try {/* 这里什么也没做*/ } finally { from.Money -= amount; //现在,这里不可能发生线程终止(由于Thread.Abort/AppDomain.Unload) to.Money += amount; } }
但是,绝不建议将所有代码都放到finally块中!这个技术只适合于修改及其敏感的数据。
●可以用System.Diagnostics.Contracts.Constract类向方法应用代码契约。
●可以使用约束执行区域(Constrained Excecution Region,CER),它提供了消除CLR不确定性的一种方式。
●可利用事务(transaction)来确保状态要么修改,要么都不修改。如TransactionScope类。
●将自己的方法设计的更明确。如下面的Monitor类实现线程同步:
public static class SomeType { private static readonly object s_lockObject = new object(); public static void SomeMethod() { Monitor.Enter(s_lockObject);//如果抛出异常,是否获取了锁? //如果已经获取了锁,它就得不到释放 try { //在这里执行线程安全的操作 } finally { Monitor.Exit(s_lockObject); } } }
由于存在上面展示的问题,这个重载的Monitor的Enter方法已经不再鼓励使用,建议像下面这样写:
public static class SomeType { private static readonly object s_lockObject = new object(); public static void SomeMethod() { bool lockTaken = false;//假定没有获取锁 try { Monitor.Enter(s_lockObject,ref lockTaken);//无论是否抛出异常,以下代码都能正常工作 //在这里执行线程安全的操作 } finally { //如果以获取就释放它。 if(lockTaken == true) Monitor.Exit(s_lockObject); } } }
虽然以上代码变得更明确,但在线程同步锁的情况下,现在的建议是根本不要随同异常处理使用它们。
●如果确定状态以损坏到无法修改的程度,就应销毁所有损坏的状态,防止它造成更多的伤害。然后重启应用程序,将应用程序恢复到一个良好的状态。由于托管代码不能泄露到一个AppDomain的外部,你可以调用AppDomain的Unload方法来卸载整个AppDomain。如果觉得状态过于糟糕,以至于需要终止这个进程,你可以调用Environment的FailFast方法。这个方法中可以指定异常消息,调用这个方法时,不会运行任何活动的try/finally块或者Finalize方法。然后它会将消息发送个Windows Application的日志。
我们认为finally块非常强悍!不管线程抛出什么样的异常,finally块中的代码都保证会执行。应该用finally块清理那些已成功启动的操 作,然后再返回调用者或执行finally块之后的代码。Finally块还经常用于显示释放对象以避免资源泄漏。如下例:
public static void SomeMethod() { FileStream fs = new FileStream(@"c:\test.txt", FileMode.Open); try { //显示用100除以文件第一个字节的结果 Console.WriteLine(100 / fs.ReadByte()); } finally { //清理资源,即使发生异常,文件都能关闭 fs.Close(); } }
确保清理代码的执行时如此重要,以至于许多编程语言都提供了一些构造来简化清理代码的编写。例如:只要使用了lock,using和foreach语 句,C#编译器就会自动生成try/finally块。另外,重写类的析构器(Finalize)时,C#编译器也会自动生成try/catch块。使用 这些构造时,编译器将你写的代码放到try块内,并自动将清理代码放在finally块内,具体如下:
●使用lock语句,锁会在finally块中释放。
●使用using语句,会在finally块中调用对象的Dispose方法。
●使用foreach语句,会在finally块中调用IEnumerator对象的Dispose方法。
●定义析构方法时,会在finally块调用基类的Finalize方法。
例如,用using语句代替上面的代码,代码量更少,但编译后的结果是一样的。
public static void SomeMethod() { using (FileStream fs = new FileStream(@"c:\test.txt", FileMode.Open)) { Console.WriteLine(100 / fs.ReadByte()); } }
异常抛出时,CLR会在调用栈中向上查找与抛出的异常类型匹配的catch块。
异常抛出时,CLR会在调用栈中向上查找与抛出异常类型匹配的catch块。如果没有找到一个匹配的catch块,就发生一个未处理异常。CLR检测到进程中的任何线程有一个未处理的异常,就会终止进程。Microsoft的每种应用程序都有自己的与未处理异常打交道的方式。
●对于任何应用程序,查阅System.Domain的UnhandledException事件。
●对于WinForm应用程序,查阅System.Windows.Forms.NativeWindow的 OnThreadException虚方法,System.Windows.Forms.Application的OnThreadException虚 方法,System.Windows.Forms.Application的ThreadException事件。
●对于WPF应用程序,查阅System.Windows.Application的 DispatcherUnhandledException事件和System.Windows.Threading.Dispatcher的 UnhandledException和UnhandledExceptionFilter事件。
●对于Silverlight,查阅System.Windows.Forms.Application的ThreadException事件。
●对于ASP.NET应用程序,查阅System.Web.UI.TemplateControl的Error事件。 TemplateControl类是System.Web.UI.Page类和System.Web.UI.UserControl类的基类。另外还要查 询System.Web.HttpApplication的Error事件。
约束执行区是必须对错误有适应能力的一个代码块,说白点,就是这个代码块要保证可靠性非常高,尽量不出异常。看看下面这段代码:
public static void Demo1() { try { Console.WriteLine("In Try"); } finally {//Type1的静态构造器在这里隐式调用 Type1.M(); } } private sealed class Type1 { static Type1() { //如果这里抛出异常,M就得不到调用 Console.WriteLine("Type1‘s static ctor called."); } public static void M() { } }
运行上述代码,得到以下的结果:
In Try
Type1‘s static ctor called.
我们希望的目的是,除非保证finally块中的代码得到执行,否则try块中的代码根本就不要开始执行。为了达到这个目的,可以像下面这样修改代码:
public static void Demo1() { //强迫finally的代码块提前准备好 RuntimeHelpers.PrepareConstrainedRegions(); try { Console.WriteLine("In Try"); } finally {//Type1的静态构造器在这里隐式调用 Type1.M(); } } private sealed class Type1 { static Type1() { //如果这里抛出异常,M就得不到调用 Console.WriteLine("Type1‘s static ctor called."); } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static void M() { } }
得到的结果如下:
Type1‘s static ctor called.
In Try
PrepareConstrainedRegions是个非常特别的方法,JIT编译器遇到这个方法,就会提前编译与try关联的catch和 finally块中的代码。JIT编译器会加载任何程序集,创建任何类型,调用任何静态构造器,并对方法进行JIT编译,如果其中的任何操作发生异常,这 个异常会在try块钱抛出。
需要JIT提前准备的方法必须要应用ReliabilityContract特性,并且向这个特性传递的参数必须是 Consistency.WillNotCorruptState或Consistency.MayCorruptInstance。这是由于假如方法会 损坏AppDomain或进程的状态,CLR便无法对状态的一致性做出任何保证。请确保finally块中只有刚刚描述的应用了 ReliabilityContract特性的方法。向ReliabilityContract传递的另一个参数Cer.Success,表示保证该方法 不会失败,否则用Cer.MayFail。Cer.None这个值表明方法不进行CER保证。换言之,方法没有CER的概念。对于没有应用 ReliabilityContract特性的方法等价于下面这样
[ReliabilityContract(Consistency.MayCorruptProcess, Cer.None)]
迫使JIT编译器预先准备的还有几个静态方法,它们都定义在RuntimeHelper中:
public static void PrepareMethod(RuntimeMethodHandle method);
public static void PrepareMethod(RuntimeMethodHandle method, RuntimeTypeHandle[] instantiation);
public static void PrepareDelegate(Delegate d);
public static void PrepareContractedDelegate(Delegate d);
还应关注下RuntimeHelpers 的ExecuteCodeWithGuaranteedCleanup这个方法,它是在资源保证得到清理的前提下执行代码的另一种方式:
public static void ExecuteCodeWithGuaranteedCleanup(RuntimeHelpers.TryCode code, RuntimeHelpers.CleanupCode backoutCode, object userData);
调用这个方法要将try和finally块的主体作为回调方法传递,他们的原型要分别匹配以下的两个委托:
public delegate void TryCode(object userData);
public delegate void CleanupCode(object userData, bool exceptionThrown);
最后,另一种保证代码得以执行的方式是使用CriticalFinalizerObject类。
代码契约(code contract)提供了直接在代码中申明代码设计决策的一种方式。
●前条件 一般用于参数的验证。
●后条件 方法因为一次普通的返回或者因为抛出一个异常而终止时,对状态进行验证。
●对象不变性(object Invariant) 用于对象的整个生命期内,保持对象字段的良好性状态。
代码契约有利于代码的使用、理解、进化、测试、文档和初期错误检查。可将前条件、后条件和对象不变性想象为方法签名的一部分。所以,代码新版本的契约可以变得更宽松,但是,除非破坏向后兼容性,否则代码新版本的契约不能变得更严格。
代码契约的核心是静态类System.Diagnostics.Contracts.Contract。由于该技术较新,实际中运用机会不多,故不再投入大量精力去研究。具体用时可以查阅MSDN相关文档。
标签:
原文地址:http://www.cnblogs.com/chrisghb8812/p/5551331.html