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地封装。

相关文章
相关标签/搜索