Thread.Abort() Is Evil.
文章标题是看的国外的一篇文章中的小标题,我想不出更好的汉语标题来表达这篇文章的含义。
首先,让我们从介绍thread.Abort()开始。
MS对thread.Abort()给出的解释是:在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。 调用此方法通常会终止线程。但其实并没有这几句话那么简单。首先看一个实验,尝试终止主线程
static voidMain(string[] args) { try { Thread.CurrentThread.Abort(); } catch { //Thread.ResetAbort(); Console.WriteLine("主线程接受到被释放销毁的信号"); Console.WriteLine( "主线程的状态:{0}",Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("主线程最终被被释放销毁"); Console.WriteLine("主线程的状态:{0}",Thread.CurrentThread.ThreadState); Console.ReadKey(); } }
运行结果:
从运行结果上看很容易看出当主线程被终止时其实报出了一个ThreadAbortException, 从中我们可以进行捕获,但是注意的是,主线程直到finally语句块执行完毕之后才真正结束(可以仔细看下主线程的状态一直处于AbortRequest),真正销毁主线程主线程是在finally语句块中,在try{}catch{}中并不能完成对主线程的销毁。
同样,我们看一个相似的例子,销毁工作线程。
static voidTestAbort() { try { Thread.Sleep(10000); } catch { Console.WriteLine("线程{0}接受到被释放销毁的信号",Thread.CurrentThread.Name); Console.WriteLine("捕获到异常时线程{0}主线程的状态:{1}",Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState); } finally { Console.WriteLine("进入finally语句块后线程{0}主线程的状态:{1}", Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState); } } Main: static voidMain(string[] args) { Thread thread1 = new Thread(TestAbort); thread1.Name = "Thread1"; thread1.Start(); Thread.Sleep(1000); thread1.Abort(); thread1.Join(); Console.WriteLine("finally语句块后,线程{0}主线程的状态:{1}",thread1.Name, thread1.ThreadState); Console.ReadKey(); }
运行结果:
这个例子验证了我们上面得出的结论:真正销毁主线程主线程是在finally语句块中,在try{}catch{}中并不能完成对主线程的销毁。
另外,如果对一个尚未启动的线程调用Abort的话,一旦该线程启动就会被停止。如果在已挂起的线程上调用 Abort,则将在调用 Abort 的线程中引发 ThreadStateException,并将 AbortRequested 添加到被中止的线程的ThreadState 属性中。直到调用 Resume 后,才在挂起的线程中引发 ThreadAbortException。如果在正在执行非托管代码的托管线程上调用 Abort,则直到线程返回到托管代码才引发 ThreadAbortException。
这些看上去并没有什么不足,但这里我们要注意:ThreadAbortException是一个异步异常。
那么什么是异步异常呢?异步异常不是某条语句立即引发的,而是在语句执行后,在整个运行期间都有可能发生的。在异步操作中,函数发起操作但并不等待操作完成就返回成功的消息。异步操作更类似于一个行动的发起,只要该行动被发起,便表示发起行为获得成功。至于行动本身在后续执行中是否顺利,发起语句并不负责。显然,异步异常发生时,主线程不能确定程序究竟执行到了何处,也即是异常可能发生在整个工作线程中代码的任何位置,而且也无法在主线程中捕获异常对象。这便是ThreadAbortException的邪恶之处,在销毁工作线程之前你无法判断工作线程的工作状态。或许你会说:“哦,这没什么,反正我都已经决定要销毁这个工作线程了”。如果你真这么认为,那么请继续看下面这种情况。
或许你已经对以下代码习以为常:
using (FileStream fs= File.Open(myDataFile, FileMode.Open, FileAccess.ReadWrite,FileShare.None)) { ...do stuff with data file... }
这种打开文件的方式确实使你的程序更加健壮与安全,它等价于以下代码:
FileStream fs =File.Open(myDataFile, FileMode.Open, FileAccess.ReadWrite,FileShare.None); try { ...do stuff with data file... } finally { IDisposable disp = fs; disp.Dispose(); }
不论你对文件的打开、操作过程如何,编译器最终都会在finally语句块中将文件流关闭,之所以这种方式使程序安全运行也是出于这一点,但异步异常的存在却打破了这一点。试想如果在工作线程即将开始执行finally语句块之初被主线程执行workthread.Abort(),ThreadAbortException发生在IDisposable disp=fs;或之前,那么工作线程便不能正常执行文件流fs的关闭,文件将会一直处于打开状态,这种状态可能一直维持到到你的程序完全退出。不得不承认这种情况有可能发生。在这期间,其他程序若需要对这个文件执行打开和读写,就得一直等到你的程序完全退出才行,如果你的程序是要一直运行几十天几个月的服务器程序的话,那这种情况显得更加糟糕。当然这个反例只是一种情况,异步异常使程序处于不受控制的状态,也即是你不知道会出现什么样的情况。
正是异步异常的这种不确定性,也正是Thread.Abort()总会导致ThreadAbortException,所以便印证了了这句话:Thread.Abort() Is Evil。
那么又该怎么解决这个问题呢?或许你会应用另一个方法,Thread.Interrupt(),在工作线程的安全点引发ThreadInterruptException,然后在这个异常的catch语句中使用Thread.CurrentThread.Abort()来销毁工作线程,但是我强烈建议你不要这么做。Thread.Interrupt()被设计的本意是中断目标线程(工作线程)的等待,继续它的运行,而不是为了让线程运行到安全点进行销毁而存在。况且你如果这么使用Thread.Interrupt(),岂不是又少了一个控制工作线程的工具。
真正值得建议停止线程的方法是使用volatile bool变量,为你的工作线程设置一个状态量。当主线程发出让工作线程停止的信号时,就友好的停止工作线程。我们且看MS给出的例子:
public class Worker { // This method is called when the thread isstarted. public void DoWork() { while (!_shouldStop) { Console.WriteLine("Workerthread: working..."); } Console.WriteLine("Worker thread:terminating gracefully."); } public void RequestStop() { _shouldStop = true; } // Keyword volatile is used as a hint tothe compiler that this data // member is accessed by multiple threads. private volatile bool _shouldStop; } public class WorkerThreadExample { static void Main() { // Create the worker thread object.This does not start the thread. Worker workerObject = new Worker(); Thread workerThread = newThread(workerObject.DoWork); // Start the worker thread. workerThread.Start(); Console.WriteLine("Main thread:starting worker thread..."); // Loop until the worker threadactivates. while (!workerThread.IsAlive) ; // Put the main thread to sleep for 1millisecond to // allow the worker thread to do somework. Thread.Sleep(1); // Request that the worker thread stopitself. workerObject.RequestStop(); // Use the Thread.Join method to blockthe current thread // until the object's threadterminates. workerThread.Join(); Console.WriteLine("Main thread:worker thread has terminated."); } }
运行结果:
受volatile方法的启发,你或许会写出以下的这种方法:
public class Worker { readonly object stopLock = new object(); bool stopping = false; bool stopped = false; public bool Stopping { get { lock (stopLock) { return stopping; } } } public bool Stopped { get { lock (stopLock) { return stopped; } } } public void Stop() { lock (stopLock) { stopping = true; } } void SetStopped() { lock (stopLock) { stopped = true; } } public void Run() { try { while (!Stopping) { //do your work } } finally { SetStopped(); } } }
我不知道为变量加锁的方法会不会对工作线程的运行效率带来怎样的影响,所以我很难说向你说明推荐这种方法或不推荐这种方法。但这确实是一种友好结束线程的方法之一。我真正推荐的方法除了使用volatile bool外,还有下面这种方法,运用事件通信模型友好地停止线程。
ManualResetEvent _requestTermination = newManualResetEvent(false); ManualResetEvent _terminated = newManualResetEvent(false); public void Init() { new Thread(new ThreadStart(() => { while (!_requestTermination.WaitOne(0)) { // do something usefull } _terminated.Set(); })).Start(); } public void Dispose() { _requestTermination.Set(); // you could enter a maximum wait timein the WaitOne(...) _terminated.WaitOne(); }
在Dispose()中,程序一直会等到工作线程正常结束。不得不说,事件通信模型向来是处理棘手问题的能手。
That's all,转载请标明出处。