在前一篇我們已經提到過Mutex和本篇的主角們直接或間接http://www.php.cn/code/6064.html" target="_blank">繼承自 WaitHandle:
WaitHandle提供了若干用於同步的方法。上一篇關於Mutex的blog中已經講到一個WaitOne(),這是一個執行個體方法。除此之外,WaitHandle另有3個用於同步的靜態方法:
SignalAndWait(WaitHandle, WaitHandle):以原子操作的形式,向第一個WaitHandle發出訊號並等待第二個。即喚醒阻塞在第一個WaitHandle上的線程/進程,然後自己等待第二個WaitHandle,且這兩個動作是原子性的。跟WaitOne()一樣,這個方法另有兩個重載方法,分別用Int32或者TimeSpan來定義等待逾時時間,以及是否從內容相關的同步域中退出。
WaitAll(WaitHandle[]):這是用於等待WaitHandle數組裡的所有成員。如果一項工作,需要等待前面所有人完成才能繼續,那麼這個方法就是一個很好的選擇。仍然有兩個用於控制等待逾時的重載方法,請自行參閱。
WaitAny(WaitHandle[]):與WaitAll()不同,WaitAny只要等到數組中一個成員收到訊號就會返回。如果一項工作,你只要等最快做完的那個完成就可以開始,那麼WaitAny()就是你所需要的。它同樣有兩個用於控制等待逾時的重載。
線程相關性
Mutex與Monitor一樣,是具有線程相關性的。我們之前已經提到過,只有通過Monitor.Enter()/TryEnter()獲得對象鎖的線程才能調用Pulse()/Wait()/Exit();同樣的,只有獲得Mutex擁有權的線程才能執行ReleaseMutex()方法,否則就會引發異常。這就是所謂的線程相關性。
相反,EventWaitHandle以及它的衍生類別AutoResetEvent和ManualResetEvent都是線程無關的。任何線程都可以發訊號給EventWaitHandle,以喚醒阻塞在上面的線程。
下一篇要提到的Semaphore也是線程無關的。
Event通知
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”和“終止”似乎並不對應,細想起來這兩者的說法其實也不矛盾。事件如果在進行中,當然就沒有“終止”,那麼其它線程就需要等待;一旦事件完成,那麼事件就“終止”了,於是我們發送訊號喚醒等待的線程,所以“訊號已發送”狀態也是合理的。兩個小細節:
無論中文還是英文版,都提到這個方法都是可以讓“一個”或“多個”等待線程“繼續/Proceed”(注意不是“喚醒”)。所以這個方法在“喚醒”這個動作上是類似於Monitor.Pulse()和Monitor.PulseAll()的。至於什麼時候類似Pulse(),又在什麼時候類似PulseAll(),往下看。
這個方法有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眾多建構函式中最簡單的一個:
好了,現在我們可以清楚的知道Set()在什麼時候分別類似於Monitor.Pulse()/PulseAll()了:
當EventWaitHandle工作在AutoReset模式下,就喚醒功能而言,Set()與Monitor.Pulse()類似。此時,Set()只能喚醒眾多(如果有多個的話)被阻塞線程中的一個。但兩者仍有些差別:
Set()的作用不僅僅是“喚醒”而是“釋放”,可以讓線程繼續工作(proceed);相反,Pulse()喚醒的線程只是重新進入Running狀態,參與對象鎖的爭奪,誰都不能保證它一定會獲得對象鎖。
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或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更需要注意,因為有可能駭客程式用相同的事件名對你的線程發送訊號或者進行組織,那樣可能會嚴重危害你的商務邏輯。
MSDN Demo
using System;using System.Threading;public class Example{ // The EventWaitHandle used to demonstrate the difference // between AutoReset and ManualReset synchronization events. // private static EventWaitHandle ewh; // A counter to make sure all threads are started and // blocked before any are released. A Long is used to show // the use of the 64-bit Interlocked methods. // private static long threadCount = 0; // An AutoReset event that allows the main thread to block // until an exiting thread has decremented the count. // private static EventWaitHandle clearCount = new EventWaitHandle(false, EventResetMode.AutoReset); [MTAThread] public static void Main() { // Create an AutoReset EventWaitHandle. // ewh = new EventWaitHandle(false, EventResetMode.AutoReset); // Create and start five numbered threads. Use the // ParameterizedThreadStart delegate, so the thread // number can be passed as an argument to the Start // method. for (int i = 0; i <= 4; i++) { Thread t = new Thread( new ParameterizedThreadStart(ThreadProc) ); t.Start(i); } // Wait until all the threads have started and blocked. // When multiple threads use a 64-bit value on a 32-bit // system, you must access the value through the // Interlocked class to guarantee thread safety. // while (Interlocked.Read(ref threadCount) < 5) { Thread.Sleep(500); } // Release one thread each time the user presses ENTER, // until all threads have been released. // while (Interlocked.Read(ref threadCount) > 0) { Console.WriteLine("Press ENTER to release a waiting thread."); Console.ReadLine(); // SignalAndWait signals the EventWaitHandle, which // releases exactly one thread before resetting, // because it was created with AutoReset mode. // SignalAndWait then blocks on clearCount, to // allow the signaled thread to decrement the count // before looping again. // WaitHandle.SignalAndWait(ewh, clearCount); } Console.WriteLine(); // Create a ManualReset EventWaitHandle. // ewh = new EventWaitHandle(false, EventResetMode.ManualReset); // Create and start five more numbered threads. // for(int i=0; i<=4; i++) { Thread t = new Thread( new ParameterizedThreadStart(ThreadProc) ); t.Start(i); } // Wait until all the threads have started and blocked. // while (Interlocked.Read(ref threadCount) < 5) { Thread.Sleep(500); } // Because the EventWaitHandle was created with // ManualReset mode, signaling it releases all the // waiting threads. // Console.WriteLine("Press ENTER to release the waiting threads."); Console.ReadLine(); ewh.Set(); } public static void ThreadProc(object data) { int index = (int) data; Console.WriteLine("Thread {0} blocks.", data); // Increment the count of blocked threads. Interlocked.Increment(ref threadCount); // Wait on the EventWaitHandle. ewh.WaitOne(); Console.WriteLine("Thread {0} exits.", data); // Decrement the count of blocked threads. Interlocked.Decrement(ref threadCount); // After signaling ewh, the main thread blocks on // clearCount until the signaled thread has // decremented the count. Signal it now. // clearCount.Set(); }}