码迷,mamicode.com
首页 > 其他好文 > 详细

第20章 异常和状态管理20.7-20.13

时间:2016-06-01 23:16:59      阅读:410      评论:0      收藏:0      [点我收藏+]

标签:

20.7用可靠性换取开发效率

面向对象编程,编译器功能,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的外部,你可以调用AppDomainUnload方法来卸载整个AppDomain。如果觉得状态过于糟糕,以至于需要终止这个进程,你可以调用EnvironmentFailFast方法。这个方法中可以指定异常消息,调用这个方法时,不会运行任何活动的try/finally块或者Finalize方法。然后它会将消息发送个Windows Application的日志。

20.8指导原则和最佳实践

我们认为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());
            }         
        }

20.9未处理的异常

异常抛出时,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事件。

20.10对异常进行调试

 

20.11 异常处理的性能问题

20.12 约束执行区域 (CER)

约束执行区是必须对错误有适应能力的一个代码块,说白点,就是这个代码块要保证可靠性非常高,尽量不出异常。看看下面这段代码:

 

       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类。

20.13 代码契约

  代码契约(code contract)提供了直接在代码中申明代码设计决策的一种方式。

●前条件 一般用于参数的验证。

●后条件 方法因为一次普通的返回或者因为抛出一个异常而终止时,对状态进行验证。

●对象不变性(object Invariant) 用于对象的整个生命期内,保持对象字段的良好性状态。

代码契约有利于代码的使用、理解、进化、测试、文档和初期错误检查。可将前条件、后条件和对象不变性想象为方法签名的一部分。所以,代码新版本的契约可以变得更宽松,但是,除非破坏向后兼容性,否则代码新版本的契约不能变得更严格。

代码契约的核心是静态类System.Diagnostics.Contracts.Contract。由于该技术较新,实际中运用机会不多,故不再投入大量精力去研究。具体用时可以查阅MSDN相关文档。

 

第20章 异常和状态管理20.7-20.13

标签:

原文地址:http://www.cnblogs.com/chrisghb8812/p/5551331.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!