.NET 同步與異步 之 EventWaitHandle(Event通知) (十三)

本隨筆續接:.NET 同步與異步 之 Mutex (十二)html

在前一篇咱們已經提到過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()就是你所須要的。它一樣有兩個用於控制等待超時的重載。

 

線程相關性異步

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

 

Event通知ide

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

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

    • 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()這個事件的線程都會再次被擋在門外。

 

構造函數post

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

  • 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()了:.net

  • 當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更須要注意,由於有可能黑客程序用相同的事件名對你的線程發送信號或者進行組織,那樣可能會嚴重危害你的業務邏輯。

 

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

 

 

附,Demo : http://files.cnblogs.com/files/08shiyan/ParallelDemo.zip

參見更多:隨筆導讀:同步與異步

(該系列隨筆暫告一段落)

相關文章
相關標籤/搜索