统一的线程异常处理
在一个Service程序中, 通常都会有多个Worker线程,它们可能单独运行, 也可能在一个ThreadPool中运行。为了不至于使Worker线程的未处理异常导致主程序的崩溃,我们需要对所有的工作线程以一种一致的方式处理异常,例如通知主线程,然后根据不同的异常做不同的处理,最后优雅地停止该有问题的线程。 例如以下程序:
static void Main(string[] args) { Thread thread1 = new Thread((ThreadStart)Worker_1); thread1.Start(); Thread thread2 = new Thread((ThreadStart)Worker_2); thread2.Start(); thread1.Join(); thread2.Join(); } static void Worker_1() { try { // Do something here. } catch (Exception e) { // TODO, handler exception, // Notify the main thread and stop this thread gracefully. } } static void Worker_2() { try { // Do something here. } catch (Exception e) { // TODO, handler exception, // Notify the main thread and stop this thread gracefully. } }
在该程序中,我们有 Worker_1 和 Worker_2两个工作线程,它们有相同的异常处理过程。但是问题是,当任务的种类多了起来,如Worker_3, Worker_4, 所有的这样的线程函数都要做相同的异常处理,就导致了不必要的重复,并且很容易遗忘。怎样去除这种重复呢?首先想到的是一个方案是,提供一个辅助函数,它接受一个Action作为参数:
static void SafeThread(Action action) { try { action(); } catch (Exception e) { // TODO, handler exception, // Notify the main thread and stop this thread gracefully. } }
然后Worker_1 可以这么写:
static void Worker_1() { SafeThread(delegate { // Do something here. }); }
这样是能简化一些。但这种做法会使原来的Worker方法有一个奇怪的包装,而且依然要求我们对每一个Worker做同样的处理。既然Thread的构造函数接受一个 ThreadStart的参数,我们能不能把一个原始的直接的Worker 方法(也是 ThreadStart类型)转换为一个可以处理异常的 ThreadStart 类型呢? 是可以的。首先我们定义这个转换函数如下:
static ThreadStart SafeThread(ThreadStart threadStart) { return () => { try { threadStart(); } catch (Exception e) { // TODO, handler exception, // Notify the main thread and stop this thread gracefully. } }; }
那么我们的Worker线程会很直接:
static void Worker_1() { Console.WriteLine("Worker 1"); // Do something here. } static void Worker_2() { Console.WriteLine("Worker 2"); // Do something here. }
而主程序则需要稍加改动,但也非常简单:
static void Main(string[] args) { Thread thread1 = new Thread(SafeThread(Worker_1)); thread1.Start(); Thread thread2 = new Thread(SafeThread(Worker_2)); thread2.Start(); thread1.Join(); thread2.Join(); }
这对线程函数的编写者来说, 减轻了很多负担, 也不至于会遗漏掉某个线程没被处理。做一次简单的搜索就可以解决问题。
对于接受一个参数的线程(ParameterizedThreadStart)和线程池线程 (WaitCallback),我们又该如何处理呢?