2005.1.11 欧岩亮
课程介绍
我们一起来讨论.NET中处理异常的策略,看看处理异常的最好方法是什么
基础内容
理解如何使用Microsoft .NET Framework进行编程
VS.NET使用
了解C#或VB.NET
课程内容
设计异常处理
.NET中基本的异常处理语法
异常的优先级
如何抛出异常
最佳的异常处理方法
异常处理的应用程序块(Application Block)
理解异常是一定会发生的
大多数的软件系统都不是百分之百可靠的!
要站在异常一定可能会发生的角度来编写异常处理程序,应对程序有可能发生的错误。
建立一个良好的异常处理策略。
基本的异常处理语法
所有的异常都是从Exception派生的。
演示一
异常处理
Catch块的优先性
多个Catch块的情况下,Catch块能够过滤异常,我们应该先截获具体的异常,再截获一般的异常。在写异常捕获的时候,捕获异常的顺序很重要,如果异常已经被某个Catch捕获了,那么后面的Catch就不会再处理该异常了。
抛出异常
抛出异常是为了告诉程序调用者,程序产生的错误结果是由于他们的调用引起的。
例如:一个不可能实现的接口可能会抛出一个"NotSupportedException"的异常。
使用通用的Framework异常:在开发过程中,可能会抛出"NotImplementedException"的异常。
还可以从系统异常派生出自己的具体异常,为具体的应用程序所使用。
一般派生自己异常的时候,推荐派生自System.ApplicationException这个类,这指的是一个系统级别的异常,所以我们编写应用程序的时候应该遵循的一个规则。
抛出异常的语法
演示二
抛出异常
增加一个捕获异常的Catch块
运行结果
如果是我们主动抛出异常,那么我们最好抛出具体的一个异常,而不是只抛出Exception类型的异常。这样能方面我们捕获对应类型的异常。
处理未预料的异常
确保所有的程序入口都使用了try-catch
在catch中截获所有的异常
异常处理技术
1.记录异常
在文件中记录异常
在数据库中记录异常
在Eventlog中记录异常
2.发送E-mail来通知异常
3.异常产生是,用友好(user-friendly)的方式通知用户
演示三
处理未预料的异常
非授权访问的异常。这里默认是选中第二个按钮
IO的异常
基本异常,捕获基本异常也可以只写catch,不写后面的Exception内容。这样也是能捕获所有异常的,但是它不能够具体地知道异常的内容。
Finally,鼠标指针还原
运行结果
添加一个全局异常处理函数
我们虽然没有在所有的应用程序入口写捕获异常的try-catch块,但是使用Application对象中的ThreadException属性可以设置一个delegate来捕获所有未处理的Main UI线程中出现的异常
这种方式只能处理主线程当中的异常,其他工作线程、辅助线程在异常时捕获不到的。
演示四
全局异常处理
ServiceNotification是指当前的对话框会置于所有程序的最前方。
我们在这里直接抛出异常,并且不用try-catch包装
这里全局异常处理就会截获到这个异常。
工作线程(Worker Threads)中的异常
编写多线程代码时,必须考虑在工作线程中出现的异常
在线程的入口使用try-catch
使用delegate或其他的方式将发生的异常通知给主线程
演示五
工作线程中的异常
创建新线程
非主线程中抛出异常,我们使用BeginInvoke来通知主线程。
在多线程之间互操作的时候,我们的工作线程如果想要访问主线程里面的界面元素,我们必须通过BeginInvoke来访问。也就是说,属于哪一个线程的界面元素,必须由哪一个线程来进行访问。我们必须去调用对应界面元素线程中的BeginInvoke方法,来访问它自己的界面元素。
在EmulateLongProcess执行在另外一个线程内部,如果这个方法里面发生了异常,那么catch就会捕获到这个异常,这个线程去调用BeginInvoke方法。这里的BeginInvoke方法一定是this.BeginInvoke。这里的this一定是当前的frmCustomer。
frmCustomer将完成BeginInvoke方法,让当前这个form去执行WorkerThreadExceptionHandler方法。我们通过一个delegate方式来执行。
这里WorkerThreadExceptionHandler里面又调用了一个主线程异常的delegate,把异常抛出给主线程。
我们不能直接在catch块中调用WorkerThreadExceptionHandler方法,因为这个方法里面需要访问主线程的界面元素,因此我们必须使用BeginInvoke方法来让主线程自己去调用这个方法。
当然这里我们可以不用this.BeginInvoke,而是用this.TextBox1.BeginInvoke。因为这里BeginInvoke最根本的含义是让主线程来完成这个方法。
运行结果,异常被正确地捕获到
这里如果我们把工作线程调用函数的try-catch块去掉,并在里面抛出异常
运行结果,异常一定是发生了,而界面程序没有任何反应,这样用户就会很诧异。
这就是说,如果工作线程不去做异常处理,那么这个异常就会消失了,将没有人会了解这个异常究竟是什么。
异常处理的最好方法
不要:
Catch异常并re-throw
因为重新抛出一个新的异常,会损失一些消息,例如会损失这个异常中带有的一些调用堆栈的信息。
通过抛出异常来控制代码的执行。
在程序的构造函数入口处添加try-catch方法/属性/构造
MessageBox.Show(MyException.ToString())
使用了try-catch但是并没有处理异常
需要:
从始至终要紧记异常处理的策略
在应用程序所有的入口处使用try-catch:事件处理函数,主函数,线程入口
处理所有意料到的异常
编写代码时要注意考虑到应用程序最差的情况
显示有好的信息,并提供适当的管理员联系信息
在可能的情况下提供可能的选择(终止,重试,忽略)
异常处理程序块
Publisher/subscriber设计模式
下载异常处理程序块
编译工程
在新的工程中添加引用
引入名称空间Microsoft.ApplicationBlock.ExceptionManagement
使用ExceptionManager.Publish()来发布异常
配置app.config文件启用异常管理
在.config文件中配置一些信息可以添加自己的异常处理模块
配置异常处理应用程序块
演示六
异常处理程序块
首先加入两个已有的微软提供的异常项目,并添加引用
然后在主线程异常中放置ExceptionManager的Publish方法,并传入异常信息,这样我们就能够把这个异常信息公布出来,公布的具体位置写在config文件中。
配置文件中首先定义了一个配置区域section,名叫exceptionManagement。下面就有一个exceptionManagement配置块。它的属性mode设置为on表示下面所有配置publisher的功能全部启用。logname是在日志管理里面日志的名称。applicationname是日志中记录的应用程序名称。
当出现了异常之后,我们同时也公布了这个异常。
打开Administrative Tools的Event Viewer
能看见我们这个异常日志
总结
接受现实:异常是一定会发生的
定制一套异常处理的策略
不能盲目的来处理异常
.NET中的结构化异常处理是一个强大有效地工具
2010.10.10