文章標題是看的國外的一篇文章中的小標題,我想不出更好的漢語標題來表達這篇文章的含義。安全
首先,讓咱們從介紹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{}中並不能完成對主線程的銷燬。工具
一樣,咱們看一個類似的例子,銷燬工做線程。oop
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(); }
運行結果:this
這個例子驗證了咱們上面得出的結論:真正銷燬主線程主線程是在finally語句塊中,在try{}catch{}中並不能完成對主線程的銷燬。線程
另外,若是對一個還沒有啓動的線程調用Abort的話,一旦該線程啓動就會被中止。若是在已掛起的線程上調用 Abort,則將在調用 Abort 的線程中引起 ThreadStateException,並將 AbortRequested 添加到被停止的線程的ThreadState 屬性中。直到調用 Resume 後,纔在掛起的線程中引起 ThreadAbortException。若是在正在執行非託管代碼的託管線程上調用 Abort,則直到線程返回到託管代碼才引起 ThreadAbortException。設計
這些看上去並無什麼不足,但這裏咱們要注意:ThreadAbortException是一個異步異常。code
那麼什麼是異步異常呢?異步異常不是某條語句當即引起的,而是在語句執行後,在整個運行期間都有可能發生的。在異步操做中,函數發起操做但並不等待操做完成就返回成功的消息。異步操做更相似於一個行動的發起,只要該行動被髮起,便表示發起行爲得到成功。至於行動自己在後續執行中是否順利,發起語句並不負責。顯然,異步異常發生時,主線程不能肯定程序究竟執行到了何處,也便是異常可能發生在整個工做線程中代碼的任何位置,並且也沒法在主線程中捕獲異常對象。這即是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,轉載請標明出處。