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();
            }
}

運行結果:
abort示例1

         從運行結果上看很容易看出當主線程被終止時其實報出了一個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();
        }


運行結果:

abort示例2

         這個例子驗證了我們上面得出的結論:真正銷燬主線程主線程是在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示例

 

         受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,轉載請標明出處。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章