在多线程编程中,未处理的异常是一个常见的问题。特别是在.NET 2.0应用程序中,如果未处理的异常发生在辅助(工作)线程中,它们可能会导致应用程序崩溃,或者更糟糕的是,被完全忽略。这种情况在生产环境中是不可接受的。为了解决这个问题,引入了SafeThread类。
SafeThread类的核心思想是将常规的CLR线程封装起来,并在try-catch块中执行委托。如果捕获到异常,它会触发一个事件。开发者可以使用这个事件来清理线程,分类异常类型,甚至启动新线程。这对于像“心跳”操作这样的线程操作来说,是非常有价值和必要的。
SafeThread的实现相对简单。由于线程依赖于委托来执行,只需要将委托的执行包装在一个try-catch块中,并在捕获异常时触发一个事件。SafeThread模仿了Thread类支持的单参数委托和无参数委托的构造函数。此外,SafeThread还支持新的动态委托(也称为匿名方法),以提供额外的便利。最后,为了完成封装,SafeThread实现了Thread类的所有公共方法和属性。
SafeThread还提供了一个ThreadCompleted事件,用于在处理完成时发出信号。SafeThread的实现过程中,尝试让SafeThread继承自Thread类,但由于Thread类是sealed的,只能重新实现接口并包装一个Thread对象。需要注意的是,SafeThread的某些属性和方法只有在包装的Thread对象存在时才有效,而Thread对象是在调用Start方法时创建的。
使用SafeThread的方式与CLR的Thread类完全相同。例如,可以使用ThreadStart,同时增加了一个ThreadException事件和一个标志来控制是否将对线程的Abort调用报告为异常。
SafeThread thrd = new SafeThread(new ThreadStart(this.threadRunner));
thrd.ShouldReportThreadAbort = true;
thrd.ThreadException += new ThreadThrewExceptionHandler(thrd_ThreadException);
thrd.ThreadCompleted += new ThreadCompletedHandler(thrd_ThreadCompleted);
thrd.Start();
SafeThread还提供了一个SimpleDelegate选项,可以与任何无参数void方法一起使用:
SafeThread thrd = new SafeThread((SimpleDelegate)this.threadRunner);
thrd.ShouldReportThreadAbort = true;
thrd.ThreadException += new ThreadThrewExceptionHandler(thrd_ThreadException);
thrd.ThreadCompleted += new ThreadCompletedHandler(thrd_ThreadCompleted);
thrd.Start();
ThreadException处理程序会传递抛出异常的SafeThread对象,以及抛出的Exception对象:
void thrd_ThreadException(SafeThread thrd, Exception ex)
{
// 在这里执行操作,比如重新启动线程
}
ThreadCompleted处理程序会传递完成处理的SafeThread对象,以及一个bool值,表示处理是否由于异常而终止,以及如果处理成功完成,则传递的Exception对象为null(在VB.NET术语中为Nothing)。
void thrd_ThreadCompleted(SafeThread thrd, bool hadException, Exception ex)
{
if (hadException)
{
// 线程由于ex中的未处理异常而提前终止
}
else
{
// 线程成功完成
}
}
SafeThread演示应用程序既人为又有趣:每个创建的SafeThread会定期使“开始SafeThread”按钮的动画在圆圈中脉动。每个创建的SafeThread会在99%的情况下使动画脉动,而在1%的情况下会抛出一个未处理的除以零异常。如果SafeThread成功存活100次迭代,它将成功完成并发出ThreadCompleted事件。ListBox显示了每个SafeThread正在发生的事情。
要使用SafeThread演示应用程序,请构建和/或运行它。多次点击“开始SafeThread”按钮——创建的SafeThread对象越多,按钮在圆圈中的移动速度就越快。点击“停止SafeThread”按钮以按创建顺序结束SafeThread对象。点击ListBox项以在MessageBox中查看完整项文本。当应用程序关闭时,所有SafeThread对象都会结束。
为了好玩,也为了回应Daniel Grunwald在下面评论中提出的一些好点子,在文章中添加了一个“不安全”线程演示应用程序,以更好地说明没有异常捕获或SafeThread会发生什么。点击“开始不安全线程”按钮启动一个常规的CLR线程,它会抛出一个未处理的除以零异常,并注意没有保护措施,得到的是发送调试报告给Microsoft的对话框,应用程序会崩溃。再次运行演示,但这次,首先勾选“使用未处理异常处理程序”复选框,然后点击“开始不安全线程”按钮。现在,得到了一个消息框,这是由AppDomain.UnhandledException事件提供的……然后,得到了发送调试报告给Microsoft的对话框,应用程序会崩溃。
这揭示了仅使用System.AppDomain.CurrentDomain.UnhandledException事件处理程序来捕获“纯”辅助线程(即不是工作线程或Timer线程)中的异常的几个问题:
SafeThread还有一些有趣的属性:
注意,在.NET 2.0中,辅助线程中未处理的异常的行为发生了变化。在.NET 1.1中,工作(ThreadPool)线程(但不是Timer线程)中的未处理异常会被AppDomain的UnhandledException事件捕获。微软在应用程序配置文件中提供了向后兼容性选项:
<runtime>
<legacyUnhandledExceptionPolicy enabled="1"/>
</runtime>
有关详细信息,请参见MSDN。