.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

参见更多:随笔导读:同步与异步

(该系列随笔暂告一段落)

相关文章
相关标签/搜索