C#線程同步(4)- 通知&EventWaitHandle一家

文章原始出處 http://xxinside.blogbus.com/logs/47523285.htmlhtml

預備知識:C#線程同步(1)- 臨界區&LockC#線程同步(2)- 臨界區&MonitorC#線程同步(3)- 互斥量 Mutex程序員

WaitHandle一家web

  在前一篇咱們已經提到過Mutex和本篇的主角們直接或間接繼承自WaitHandle數組

  WaitHandle提供了若干用於同步的方法。上一篇關於Mutex的blog中已經講到一個WaitOne(),這是一個實例方法。除此以外,WaitHandle另有3個用於同步的靜態方法:安全

  • SignalAndWait(WaitHandle, WaitHandle):以原子操做的形式,向第一個WaitHandle發出信號並等待第二個。即喚醒阻塞在第一個WaitHandle上的線程/進程,而後本身等待第二個WaitHandle,且這兩個動做是原子性的。跟WaitOne()同樣,這個方法另有兩個重載方法,分別用Int32或者TimeSpan來定義等待超時時間,以及是否從上下文的同步域中退出。
  • WaitAll(WaitHandle[]):這是用於等待WaitHandle數組裏的全部成員。若是一項工做,須要等待前面全部人完成才能繼續,那麼這個方法就是一個很好的選擇。仍然有兩個用於控制等待超時的重載方法,請自行參閱。
  • WaitAny(WaitHandle[]):與WaitAll()不一樣,WaitAny只要等到數組中一個成員收到信號就會返回。若是一項工做,你只要等最快作完的那個完成就能夠開始,那麼WaitAny()就是你所須要的。它一樣有兩個用於控制等待超時的重載。

線程相關性(Thread Affinity )dom

  EventWaitHandle和Mutex二者雖然是派生自同一父類,但有着徹底不一樣的線程相關性:ide

  • Mutex與Monitor同樣,是「線程相關(Thread Affinity)」的。咱們以前已經提到過,只有經過Monitor.Enter()/TryEnter()得到對象鎖的線程才能調用Pulse()/Wait()/Exit();一樣的,只有得到Mutex擁有權的線程才能執行ReleaseMutex()方法,不然就會引起異常。這就是所謂的線程相關性。
  • 相反,EventWaitHandle以及它的派生類AutoResetEvent和ManualResetEvent都是線程無關的。任何線程均可以發信號給EventWaitHandle,以喚醒阻塞在上面的線程。
  • 下一篇要提到的Semaphore也是線程無關的。

Mutex與Event函數

  咱們在Mutex一篇中沒有具體提到Mutex是否能發送信號,只是簡單說Mutex不太適合有相互消息通知的同步,它僅有的一些同步方法是來自其父類的靜態方法。那麼如今咱們能夠仔細來看看Mutex到底能不能用於關於Monitor那篇提到的生產者、消費者和糖罐的場景。spa

  回過頭來仔細查看Mutex的全部方法,除了一個咱們已經提到的WaitHandle上的靜態方法SingnalAndWait(toSingnal, toWaitOn),咱們找不到任何「屬於Mutex本身」的、用於發送信號的方法。退而求其次吧,咱們就來看看這個靜態方法是否可讓Mutex具備通知的能力。.net

  若是toSignal是一個Mutex,那麼收到「信號」就等效於ReleaseMutex()。而因爲Mutex的線程相關性,只有擁有當前Mutex的線程纔可以發送這個信號(ReleaseMutex),不然會引起異常。也就是說若是要用這個方法來通知其它線程同步,Mutex只能本身發給本身。與之相反,若是第二個參數toWaitOn也是個Mutex,那麼這個Mutex不能是本身。由於前篇已經講過,Mutex的擁有者能夠屢次WaitOne()而不阻塞,這裏也是同樣。因此若是Mutex必定要使用這個方法,準確的說是隻是成爲這個方法的參數,那隻能是WaitHandle.SignalAndWait(它本身,另外一個Mutex)。

  試想,若是有人試圖只使用Mutex來進行同步通知。假設生產者線程經過Mutex上的WaitOne()得到了mutexA的擁有權,而且在生產完畢後調用了SingnalAndWait(mutexA,mutexB),通知因爲當前mutexA而阻塞的消費者線程,而且將本身阻塞在mutexB上。那麼被喚醒的消費者線程得到MutexA的擁有權吃掉糖後,也只能調用SingnalAndWait(mutexA,mutexB)釋放它得到的mutexA且阻塞於MutexB。問題來了,此時的生產者是阻塞在mutexB上……也許,咱們能夠設計一段「精巧」的代碼,讓生產者和消費者一下子阻塞在mutexA,一下子阻塞在mutexB上……我不想花費這個力氣去想了,你能夠試試看:)。無論有沒有這樣的可能,Mutex很明顯就不適用於通知的場景。

EventWaitHandle的獨門祕笈

  正由於Mutex沒有很好地繼承父輩的衣鉢,EventWaitHandle以及它的兒子/女兒們便來到了這個世界上。

  EventWaitHandle、AutoResetEvent、ManualResetEvent名字裏都有一個「Event」,不過這跟.net的自己的事件機制徹底沒有關係,它不涉及任何委託或事件處理程序。相對於咱們以前碰到的Monitor和Mutex須要線程去爭奪「鎖」而言,咱們能夠把它們理解爲一些須要線程等待的「事件」。線程經過等待這些事件的「發生」,把本身阻塞起來。一旦「事件」完成,被阻塞的線程在收到信號後就能夠繼續工做。

  爲了配合WaitHandle上的3個靜態方法SingnalAndWait()/WailAny()/WaitAll(),EventWaitHandle提供了本身獨有的,使「Event」完成和從新開始的方法:

  • bool:Set():英文版MSDN:Sets the state of the event to signaled, allowing one or more waiting threads to proceed;中文版MSDN:將事件狀態設置爲終止狀態,容許一個或多個等待線程繼續。初看「signaled」和「終止」彷佛並不對應,細想起來這二者的說法其實也不矛盾。事件若是在進行中,固然就沒有「終止」,那麼其它線程就須要等待;一旦事件完成,那麼事件就「終止」了,因而咱們發送信號喚醒等待的線程,因此「信號已發送」狀態也是合理的。兩個小細節:
    1. 不管中文仍是英文版,都提到這個方法都是可讓「一個」或「多個」等待線程「繼續/Proceed」(注意不是「喚醒」)。因此這個方法在「喚醒」這個動做上是相似於Monitor.Pulse()和Monitor.PulseAll()的。至於何時相似Pulse(),又在何時相似PulseAll(),往下看。
    2. 這個方法有bool型的返回值:若是該操做成功,則爲true;不然,爲false。不過MSDN並無告訴咱們,何時執行會失敗,你只有找個微軟MVP問問了。
  • bool:Reset():Sets the state of the event to nonsignaled, causing threads to block. 將事件狀態設置爲非終止狀態,致使線程阻止。 一樣,咱們須要明白「nonsignaled」和「非終止」是一回事情。還一樣的是,仍然有個無厘頭的返回值。Reset()的做用,至關於讓事件從新開始處於「進行中」,那麼此後全部WaitOne()/WaitAll()/WaitAny()/SignalAndWait()這個事件的線程都會再次被擋在門外。

  來看看EventWaitHandle衆多構造函數中最簡單的一個:

  • EventWaitHandle(Boolean initialState, EventResetMode mode):初始化EventWaitHandle類的新實例,並指定等待句柄最初是否處於終止狀態,以及它是自動重置仍是手動重置。大多數時候咱們會在第一個參數裏使用false,這樣新實例會缺省爲「非終止」狀態。第二個參數EventResetMode是一個枚舉,一共兩個值:
    1. EventResetMode.AutoReset:當Set()被調用當前EventWaitHandle轉入終止狀態時,如有線程阻塞在當前EventWaitHandle上,那麼在釋放一個線程後EventWaitHandle就會自動重置(至關於自動調用Reset())再次轉入非終止狀態,剩餘的原來阻塞的線程(若是有的話)還會繼續阻塞。若是調用Set()後本沒有線程阻塞,那麼EventWaitHandle將保持「終止」狀態直到一個線程嘗試等待該事件,這個該線程不會被阻塞,此後EventWaitHandle纔會自動重置並阻塞那以後的全部線程。   
    2. EventResetMode.ManualReset:當終止時,EventWaitHandle 釋放全部等待的線程,並在手動重置前,即Reset()被調用前,一直保持終止狀態。

  好了,如今咱們能夠清楚的知道Set()在何時分別相似於Monitor.Pulse()/PulseAll()了:

  • 當EventWaitHandle工做在AutoReset模式下,就喚醒功能而言,Set()與Monitor.Pulse()相似。此時,Set()只能喚醒衆多(若是有多個的話)被阻塞線程中的一個。但二者仍有些差異:
    1. Set()的做用不只僅是「喚醒」而是「釋放」,可讓線程繼續工做(proceed);相反,Pulse()喚醒的線程只是從新進入Running狀態,參與對象鎖的爭奪,誰都不能保證它必定會得到對象鎖。
    2. Pulse()的已被調用的狀態不會被維護。所以,若是在沒有等待線程時調用Pulse(),那麼下一個調用Monitor.Wait()的線程仍然會被阻塞,就像Pulse() 沒有被被調用過。也就是說Monitor.Pulse()只在調用當時發揮做用,並不象Set()的做用會持續到下一個WaitXXX()。
  • 在一個工做在ManualReset模式下的EventWaitHandle的Set()方法被調用時,它所起到的喚醒做用與Monitor.PulseAll()相似,全部被阻塞的線程都會收到信號被喚醒。而二者的差異與上面徹底相同。

  來看看EventWaitHandle的其它構造函數:

  • EventWaitHandle(Boolean initialState, EventResetMode mode, String name):頭兩個參數咱們已經看過,第三個參數name用於在系統範圍內指定同步事件的名稱。是的,正如咱們在Mutex一篇中提到的,因爲父類WaitHandle是具備跨進程域的能力的,所以跟Mutex同樣,咱們能夠建立一個全局的EventWaitHandle,讓後將它用於進程間的通知。注意,name仍然是大小寫敏感的,仍然有命名前綴的問題跟,你能夠參照這裏。當name爲null或空字符串時,這等效於建立一個局部的未命名的EventWaitHandle。仍然一樣的還有,可能會由於已經系統中已經有同名的EventWaitHandle而僅僅返回一個實例表示同名的EventWaitHandle。因此最後仍舊一樣地,若是你須要知道這個EventWaitHandle是否由你最早建立,你須要使用如下兩個構造函數之一。
  • EventWaitHandle(Boolean initialState, EventResetMode mode, String name, out Boolean createdNew):createdNew用於代表是否成功建立了EventWaitHandle,true代表成功,false代表已經存在同名的事件。
  • EventWaitHandle(Boolean initialState, EventResetMode mode, String name, out Boolean createdNew, EventWaitHandleSecurity):關於安全的問題,直接查看這個構造函數上的例子吧。全局MutexEventWaitHandle的安全問題應該相對Mutex更須要注意,由於有可能黑客程序用相同的事件名對你的線程發送信號或者進行組織,那樣可能會嚴重危害你的業務邏輯。

  好啦,都差很少了,能夠寫一個例子試試了。讓咱們回到Monitor一篇中提到的生產者和消費者場景,讓咱們看看EventWaitHandle能不能完成它兄弟Mutex沒有能完成的事業。不過,即使有強大通訊能力的EventWaitHandle出馬,也避免不要使用lock/monitor或是Mutex。緣由很簡單,糖罐是一個互斥資源,必須被互斥地訪問。而EventWaitHanldle跟Mutex相反,能通訊了但卻徹底失去了臨界區的能力。因此,這個例子其實並不太適合展現EventWaitHandle的通訊機制,我只是爲了想用一樣的例子來比較這些同步機制間的差別。

  EventWaitHandle雖然還必須藉助lock/Monitor/Mutex來實現這個例子(僅僅是臨界區部分),可是它終究有強於Monitor的通訊能力,因此讓咱們來擴展一下這個例子:如今有一個生產者,有多個消費者。

  • 咱們讓消費者在沒有糖吃或吃完一塊糖後阻塞在一個工做在ManualReset模式下的EventWaitHandle,生產者在生產完畢後就經過這個事件喚醒全部消費者吃糖。因爲咱們使用了lock的關係,雖然全部消費者都被喚醒,可是他們仍是由於爭奪糖罐的關係只有一個能進入臨界區吃糖。不過此時阻塞的緣由並非由於咱們的通知時間,而是臨界區的問題。
  • 每一個消費者有一條專線,即一個工做在AutoRest模式下的EventWaitHandle,用於在吃完糖後通知生產者。而生產者用WaitAny()來等待消費者吃糖時間的發生,只要有任一消費者吃完糖,那麼生產者就試圖爭奪對糖罐的擁有權,把糖罐塞滿(一人一顆的標準)。消費者這裏使用了WaitAndSignal給生產者發消息,並等待生產者進入臨界區生產糖後通知他們。在這樣的設計邏輯下,可能糖罐中的糖尚未所有吃完生產者就有機會再次把糖罐裝滿。固然,你也可使用了WaitAll()來等待全部消費者吃完再進行生產。

using System; using System.Collections; using System.Linq; using System.Text; using System.Threading;

class WaitEventHandleSample:IDisposable {     private volatile bool _shouldStop = false; //用於控制線程正常結束的標誌     private const int _numberOfConsumer = 5;  //消費者的數目     //容器,一個只能容納一塊糖的糖盒子。PS:如今MS已經不推薦使用ArrayList,     //支持泛型的List纔是應該在程序中使用的,我這裏偷懶,不想再去寫一個Candy類了。     private ArrayList _candyBox = null;

    private EventWaitHandle _EvntWtHndlProduced = null; //生產完成的事件,ManualReset,用於通知全部消費者生產完成     private EventWaitHandle[] _EvntWtHndlConsumed = null; //消費完成的事件,AutoReset,每個消費線程對應一個事件,用於通知生產者有消費動做完成

    /// <summary>     /// 用於結束Produce()和Consume()在輔助線程中的執行     /// </summary>     public void StopThread()     {         _shouldStop = true;         //叫醒阻塞中的消費者,讓他們看到線程結束標誌         if (_EvntWtHndlProduced != null)         {             _EvntWtHndlProduced.Set();         };         //叫醒阻塞中的生產者,讓他看到線程結束標誌         if (_EvntWtHndlConsumed != null)         {             for (int i = 0; i < _numberOfConsumer; i++)             {                 if (_EvntWtHndlConsumed[i] != null)                 {                     _EvntWtHndlConsumed[i].Set();                 };             }         }     }

    /// <summary>     /// 生產者的方法     /// </summary>     public void Produce()     {         if (_candyBox == null)         {             Console.WriteLine("生產者:糖罐在哪裏?!");         }         else if (_EvntWtHndlConsumed == null)         {             Console.WriteLine("生產者:消費者們在哪裏?!");         }         else if (_EvntWtHndlProduced == null)   //這個事件用於喚醒全部消費者,所以象個喇叭         {             Console.WriteLine("生產者:喇叭壞啦,沒辦法通知消費者!");         }         else         {             //逐一檢查消費者是否到位             for (int i = 0; i < _numberOfConsumer; ++i)             {                 if (_EvntWtHndlConsumed[i] == null)                 {                     Console.WriteLine("生產者:消費者{0}在哪裏?!", i);                     return;                 }                 else                 {                     //什麼也不作                 };             };                          int numberOfSugarProduced = 0; //本次一共生產了多少顆糖

            while (!_shouldStop)             {                 lock (_candyBox)                 {                     if (_candyBox.Count < _numberOfConsumer)                     {                         numberOfSugarProduced = 0;                         while (_candyBox.Count < _numberOfConsumer)  //一共有多少個消費者就生產多少塊糖                         {                             //生產一塊糖                             _candyBox.Add("A Candy");                             ++numberOfSugarProduced;                         };                         Console.WriteLine("生產者:此次生產了{0}塊糖,罐裏如今一共有{1}塊糖!", numberOfSugarProduced, _candyBox.Count);                         Console.WriteLine("生產者:趕快來吃!!");                     }                     else //容器是滿的                     {                         Console.WriteLine("生產者:糖罐是滿的!");                     };                 };                 //通知消費者生產已完成                 _EvntWtHndlProduced.Set();                 //只要有消費者吃完糖,就開始生產                 EventWaitHandle.WaitAny(_EvntWtHndlConsumed);                 Thread.Sleep(2000);             };             Console.WriteLine("生產者:下班啦!");         }       }

    /// <summary>     /// 消費者的方法     /// </summary>     /// <param name="consumerIndex">消費者序號,用於代表使用哪一個_EvntWtHndlConsumed成員</param>     public void Consume(object consumerIndex)     {         int index = (int)consumerIndex;         if (_candyBox == null)         {             Console.WriteLine("消費者{0}:糖罐在哪裏?!",index);         }         else if (_EvntWtHndlProduced == null)         {             Console.WriteLine("消費者{0}:生產者在哪裏?!",index);         }         else if (_EvntWtHndlConsumed == null || _EvntWtHndlConsumed[index] == null)         {             Console.WriteLine("消費者{0}:電話壞啦,沒辦法通知生產者!", index);  //因爲每一個消費者都有一個專屬事件通知生產者,所以至關於電話         }         else         {             while (!_shouldStop || _candyBox.Count > 0) //即使看到結束標緻也應該把容器中的全部資源處理完畢再退出,不然容器中的資源可能就此丟失。須要指出_candybox.Count是有可能讀到髒數據的             {                 lock (_candyBox)                 {                     if (_candyBox.Count > 0)                     {                         if (!_shouldStop)                         {                             _candyBox.RemoveAt(0);                             Console.WriteLine("消費者{0}:吃了1顆糖,還剩{1}顆!!", index, _candyBox.Count);                             Console.WriteLine("消費者{0}:趕快生產!!",index);                         }                         else                         {                             Console.WriteLine("消費者{0}:我來把剩下的糖都吃了!",index);                             while (_candyBox.Count > 0)                             {                                 _candyBox.RemoveAt(0);                                 Console.WriteLine("消費者{0}:吃了1顆糖,還剩{1}顆!!", index, _candyBox.Count);                             }                             break;                         }                     }                     else                     {                         Console.WriteLine("消費者{0}:糖罐是空的!",index);                         Console.WriteLine("消費者{0}:趕快生產!!",index);                     }                 }                 WaitHandle.SignalAndWait(_EvntWtHndlConsumed[index], _EvntWtHndlProduced);                 Thread.Sleep((index+1)*1500);             }         }         Console.WriteLine("消費者{0}:都吃光啦,下次再吃!",index);     }

    /// <summary>     /// 初始化所需的各EventWaitHandle和糖罐等     /// </summary>     public void Initialize()     {         if (_candyBox == null)         {             _candyBox = new ArrayList(_numberOfConsumer); //按有多少消費者最多生產多少糖的標準初始化糖罐大小         }         else         {             //什麼也不作         }

        if (_EvntWtHndlProduced == null)         {             _EvntWtHndlProduced = new EventWaitHandle(false, EventResetMode.ManualReset);         }         else         {             //什麼也不作         }

        if (_EvntWtHndlConsumed == null)         {             _EvntWtHndlConsumed = new EventWaitHandle[_numberOfConsumer];             for (int i = 0; i < _numberOfConsumer; ++i)             {                 _EvntWtHndlConsumed[i] = new EventWaitHandle(false, EventResetMode.AutoReset);             }         }         else         {             //什麼也不作         }     }

    static void Main(string[] args)     {         WaitEventHandleSample ss = new WaitEventHandleSample();         try         {             ss.Initialize();

            //Start threads.              Console.WriteLine("開始啓動線程,輸入回車終止生產者和消費者的工做……\r\n******************************************");

            Thread thdProduce = new Thread(new ThreadStart(ss.Produce));             thdProduce.Start();

            Thread[] thdConsume = new Thread[_numberOfConsumer];             for (int i = 0; i < _numberOfConsumer; ++i)             {                 thdConsume[i] = new Thread(new ParameterizedThreadStart(ss.Consume));                 thdConsume[i].Start(i);             }

            Console.ReadLine();  //經過IO阻塞主線程,等待輔助線程演示直到收到一個回車             ss.StopThread();  //正常且優雅的結束生產者和消費者線程

            thdProduce.Join();

            for (int i = 0; i < _numberOfConsumer; ++i)             {                 thdConsume[i].Join();             }             Console.WriteLine("******************************************\r\n輸入回車結束!");             Console.ReadLine();         }         finally         {             ss.Dispose();             ss = null;         };     }

    #region IDisposable Members     public void Dispose()     {         if (_candyBox != null)         {             _candyBox.Clear();             _candyBox = null;         }         else         {             //什麼也不作         }

        if (_EvntWtHndlProduced != null)         {             _EvntWtHndlProduced.Set();             _EvntWtHndlProduced.Close();             _EvntWtHndlProduced = null;         }         else         {             //什麼也不作         }

        if (_EvntWtHndlConsumed != null)         {             for (int i = 0; i < _numberOfConsumer; ++i)             {                 if (_EvntWtHndlConsumed[i] != null)                 {                     _EvntWtHndlConsumed[i].Set();                     _EvntWtHndlConsumed[i].Close();                     _EvntWtHndlConsumed[i] = null;                 };             }             _EvntWtHndlConsumed = null;         }         else         {             //什麼也不作         };     }    #endregion }

  Produce()和Consum()中加入的Sleep代碼僅僅是爲了讓線程更爲隨機的被調度,這樣咱們能夠更容易觀察到線程亂序執行的狀況。另外,若是是一個須要跨進程同步的程序,那麼你也能夠用Mutext替換lock實現臨界區。下面是某次執行的輸出狀況,你的結果固然會跟它不一樣(空行位置是我輸入回車終止線程的時機):

開始啓動線程,輸入回車終止生產者和消費者的工做…… ****************************************** 生產者:此次生產了5塊糖,罐裏如今一共有5塊糖! 生產者:趕快來吃!! 消費者0:吃了1顆糖,還剩4顆!! 消費者0:趕快生產!! 消費者1:吃了1顆糖,還剩3顆!! 消費者1:趕快生產!! 消費者2:吃了1顆糖,還剩2顆!! 消費者2:趕快生產!! 消費者3:吃了1顆糖,還剩1顆!! 消費者3:趕快生產!! 消費者4:吃了1顆糖,還剩0顆!! 消費者4:趕快生產!! 消費者0:糖罐是空的!  消費者0:趕快生產!! 生產者:此次生產了5塊糖,罐裏如今一共有5塊糖! 生產者:趕快來吃!! 消費者1:吃了1顆糖,還剩4顆!! 消費者1:趕快生產!! 消費者0:吃了1顆糖,還剩3顆!! 消費者0:趕快生產!! 生產者:此次生產了2塊糖,罐裏如今一共有5塊糖! 生產者:趕快來吃!! 消費者0:吃了1顆糖,還剩4顆!! 消費者0:趕快生產!! 消費者2:吃了1顆糖,還剩3顆!! 消費者2:趕快生產!! 消費者1:吃了1顆糖,還剩2顆!! 消費者1:趕快生產!! 生產者:此次生產了3塊糖,罐裏如今一共有5塊糖! 生產者:趕快來吃!! 消費者0:吃了1顆糖,還剩4顆!! 消費者0:趕快生產!! 消費者3:吃了1顆糖,還剩3顆!! 消費者3:趕快生產!! 消費者4:吃了1顆糖,還剩2顆!! 消費者4:趕快生產!! 消費者0:吃了1顆糖,還剩1顆!! 消費者0:趕快生產!!

生產者:下班啦! 消費者1:我來把剩下的糖都吃了! 消費者1:吃了1顆糖,還剩0顆!! 消費者1:都吃光啦,下次再吃! 消費者0:都吃光啦,下次再吃! 消費者2:都吃光啦,下次再吃! 消費者3:都吃光啦,下次再吃! 消費者4:都吃光啦,下次再吃! ****************************************** 輸入回車結束!

AutoResetEvent & ManuResetEvent

  到此爲止咱們尚未提到過EventWaitHandle的這兩個兒子,不過這就是一兩句話的事:

  • AutoResetEvent在功能上等效於用EventResetMode.AutoReset 建立的未命名的 EventWaitHandle。
  • ManualResetEvent在功能上等效於用EventResetMode.ManualReset 建立的未命名的 EventWaitHandle。

  好了,講這麼都就夠了,這兩個子類無非是爲了方便使用而存在的。不過請記得這兩個子類永遠是局部/Local的,並不能象它們的父類同樣用於進程間的通訊。

  仍是給出一個簡單的例子,這個例子只跟通知有關,再也不涉及臨界資源。假設一個跑步比賽的場景,咱們用一個ManualResetEvent表示比賽,而後爲每一個運動員配備一個AutoResetEvent用於通知到起跑線或者是達終點。首先運動員須要到起跑線上就位,這個過程咱們讓運動員到達起跑線後調用AutoResetEvent上的Reset()發出信號,同時使用ManualResetEvent上的WaitOne()阻塞本身準備起跑。另外一方面,咱們在比賽線程上先用WaitHandle.WaitAll(AutoResetEvent[])等待全部運動員到位。WaitAll()完成後,使用ManualResetEvent上的Reset()發令開始比賽,再使用WaitHandle.WaitAny(AutoResetEvent[])等待第一個運動員衝線。而每一個運動員到終點後會再次調用AutoResetEvent.Reset()表示到達。

using System; using System.Threading; using System.Linq; using System.Text;

class Runner : IDisposable {     //用於讓全部運動員到達起跑線準備起跑     private ManualResetEvent _mnlRstEvntStartLine = null;     //用於運動員到達終點時發出信號     private static AutoResetEvent[] _mnlRstEvntRunner = null;     private const int _numberOfRunner = 8;

    private Random _rnd = new Random();          /// <summary>     /// 構造函數     /// </summary>     public Runner()     {         _mnlRstEvntStartLine = new ManualResetEvent(false);         _mnlRstEvntRunner = new AutoResetEvent[_numberOfRunner];         //請運動員就位         for (int i = 0; i < _numberOfRunner; ++i)         {             _mnlRstEvntRunner[i] = new AutoResetEvent(false);         }     }

    /// <summary>     /// 運動員方法     /// </summary>     /// <param name="id">運動員序號</param>     public void Run(object id)     {         int index = (int)id;

        //等待信號準備起跑         Console.WriteLine("{0}號運動員就位。", index);         _mnlRstEvntRunner[index].Set();

        //等待發令         _mnlRstEvntStartLine.WaitOne();

        //隨機睡眠,表示不一樣運動員跑的快慢         Thread.Sleep(_rnd.Next(2000));

        Console.WriteLine("{0}號運動員到達終點!", index);         _mnlRstEvntRunner[index].Set();     }

    /// <summary>     /// 比賽開始     /// </summary>     public void Start()     {         Thread[] runners = new Thread[_numberOfRunner];

        //請運動員就位         for (int i = 0; i < _numberOfRunner; ++i)         {             runners[i] = new Thread(Run);             runners[i].Start(i);         }         //等待全部運動員就位         WaitHandle.WaitAll(_mnlRstEvntRunner);

        //發令起跑         Console.WriteLine("***********************起跑!!!*************************");         _mnlRstEvntStartLine.Set();

        //看看誰先到達終點         int index = WaitHandle.WaitAny(_mnlRstEvntRunner);

        //等待全部運動員到達終點         //請運動員就位         for (int i = 0; i < _numberOfRunner; ++i)         {             runners[i].Join();         }         Console.WriteLine("**********************************************************");         Console.WriteLine("{0}號運動員奪得冠軍!", index);         Console.WriteLine("***********************比賽結束***************************");     }

    static void Main()     {         Runner ss = new Runner();         try         {             ss.Start();         }         catch (Exception ex)         {             Console.WriteLine(ex.Message);         }         finally         {             ss.Dispose();             ss = null;             Console.WriteLine("輸入回車結束");             Console.ReadLine();         }     }

    #region IDisposable Members

    public void Dispose()     {         if (_mnlRstEvntStartLine != null)         {             _mnlRstEvntStartLine.Set();             _mnlRstEvntStartLine.Close();         }         else         {             //do nothing         }

        if (_mnlRstEvntRunner != null)         {             for (int i = 0; i < _numberOfRunner; ++i)             {                 if (_mnlRstEvntRunner[i] != null)                 {                     _mnlRstEvntRunner[i].Set();                     _mnlRstEvntRunner[i].Close();                     _mnlRstEvntRunner[i] = null;                 }                 else                 {                     //do nothing                 }             }             _mnlRstEvntRunner = null;         }     }     #endregion }

  可能的執行結果:

0號運動員就位。 1號運動員就位。 2號運動員就位。 3號運動員就位。 4號運動員就位。 5號運動員就位。 6號運動員就位。 7號運動員就位。 ***********************起跑!!!************************* 3號運動員到達終點! 1號運動員到達終點! 0號運動員到達終點! 4號運動員到達終點! 2號運動員到達終點! 5號運動員到達終點! 6號運動員到達終點! 7號運動員到達終點! ********************************************************** 3號運動員奪得冠軍! ***********************比賽結束***************************

題外話:派生老是優雅的嗎?   在WaitHandle家族這個繼承關係裏,我實在忍不住要說「醜陋」兩個字。Mutex以及下篇將要講到的信號量Semaphore,實在是太委屈地接受了來自WaitHandle上不相關的靜態方法。WaitAll(),WaitAny(),SignalAndWait()完徹底全就是爲EventWaitHandle這一族定製的。繼承原本想體現的多態性,也僅僅是體如今這幾個方法的參數是WaitHandle上,不過有誰會真的在這幾個方法上使用Mutex或者Semaphore實例呢?也許Mutex和Semaphore是WaitHandle「抱養」的吧,不然它怎麼這麼偏愛?:)   Mutex與EventWaitHandle徹底是站在同步的兩個方向:Mutex是「鎖」能夠實現互斥訪問但幾乎不具備通訊能力;而EventWaitHandle有強大的通訊能力,但卻不能實現對資源的互斥訪問。從一個父類,派生出兩個有如此大差別的子類實在不知道是爲什麼。從這種意義上來說,彷佛Monitor比較「全面」,兩邊都能作一點。   在基礎類庫裏出現這樣的情況,彷佛確實沒法對此表示信服(這多是有些Java程序員鄙視.Net一脈的緣由之一吧,Java在語言規範和OO理論上的優雅的確有些讓人着迷:))。不過,咱們仍是要體諒一下MS。它的產品線是那麼龐大,產品生命週期是那麼持久,你不可能指望Windows API剛出現的時候就可以爲.Net將來的優雅考慮。一代代的更替中,他們總須要面對以前實現的一些限制。畢竟這幾個類的根源是比較直接地對Win32 API地封裝。

相關文章
相關標籤/搜索