安全线程管理:SafeThread类介绍

在多线程编程中,未处理的异常是一个常见的问题。特别是在.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线程)中的异常的几个问题:

  • 事件处理程序中没有足够的上下文信息来知道哪个线程引起了问题;sender参数是应用程序,而不是线程。
  • 当未处理的异常到达事件处理程序时,已经太晚了,无法采取任何措施——应用程序已经在终止了!
  • 无论如何,应用程序都会崩溃。
  • 最后——也是最不喜欢这种机制的地方——在未处理异常事件处理程序完成后,出现了可怕的发送调试报告给Microsoft的对话框!

SafeThread还有一些有趣的属性:

  • SafeThread有一个Name属性,它在Start时传递给底层的CLR线程。默认的CLR线程Name是SafeThread#XXX,其中XXX是CLR Thread对象的HashCode。
  • 当与ParameterizedThreadStart一起使用时,SafeThread记住其启动参数在ThreadStartArg属性中。
  • SafeThread提供了一个通用的Tag对象属性,这对于记住线程的任意信息很有用。
  • SafeThread提供了一个LastException属性,记录了SafeThread捕获的最后一个异常。

注意,在.NET 2.0中,辅助线程中未处理的异常的行为发生了变化。在.NET 1.1中,工作(ThreadPool)线程(但不是Timer线程)中的未处理异常会被AppDomain的UnhandledException事件捕获。微软在应用程序配置文件中提供了向后兼容性选项:

<runtime> <legacyUnhandledExceptionPolicy enabled="1"/> </runtime>

有关详细信息,请参见MSDN。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485