C#关于AutoResetEvent的使用介绍

    AutoResetEvent 允许线程通过发信号互相通信。 通常,当线程需要独占访问资源时使用该类。

    线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。

    调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持为终止状态。

    如果当 AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。 AutoResetEvent 将立即释放线程并返回到未触发状态[1]

    MSDN中如是说。但听起来很玄乎,通俗地讲,当多个线程访问共享资源时,为保证线程执行的先后顺序,就要用到该类。举例来说,A、B、C三个线程,共同访问一个变量V,但必须保证执行顺序为A-》B-》C。可以设置两个resetevent,当A执行完毕,则向B发出信号,B执行完毕,则向C发出信号。

    下面是一个网上买书的例子[2]。网上买书的执行流程如下:(1)我先选好一本书,(2)然后付钱,(3)最后卖家发货。这个顺序不能颠倒(排除货到付款的情况)。假设每个步骤一个线程,程序可以这么设计:步骤(1)为主线程,步骤(2)、(3)是两个辅助线程。代码如下:

using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Threading;

namespace CaryAREDemo
{
    class Me
    {
        const int numIterations = 550;    //买书的本数
        //付账信号控制
        static AutoResetEvent PayMoneyEvent = new AutoResetEvent(false);
        //发货信号控制    
        static AutoResetEvent DeliveEvent = new AutoResetEvent(false);
        //static ManualResetEvent PayMoneyEvent  = new ManualResetEvent(false);
        //static ManualResetEvent DeliveEvent= new ManualResetEvent(false);
        static int number; //这是关键资源

        static void Main()
        {
            Thread payMoneyThread = new Thread(new ThreadStart(PayMoneyProc));
            payMoneyThread.Name = "付账线程";
            Thread getBookThread = new Thread(new ThreadStart(GetBookProc));
            getBookThread.Name = "发货线程";
            payMoneyThread.Start();
            getBookThread.Start();

            for (int i = 1; i <= numIterations; i++)
            {
                Console.WriteLine("选书线程:数量{0}", i);
                number = i;
                //Signal that a value has been written.
                PayMoneyEvent.Set();
                Thread.Sleep(500);    //确保另外两个线程执行完毕
            }
            payMoneyThread.Abort();
            getBookThread.Abort();
        }

        static void PayMoneyProc()
        {
            while (true)
            {
                PayMoneyEvent.WaitOne();
                //PayMoneyEvent.Reset();
                Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
                DeliveEvent.Set();
            }
        }
        static void GetBookProc()
        {
            while (true)
            {
                DeliveEvent.WaitOne();
                //DeliveEvent.Reset();               
                Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
                Console.WriteLine("------------------------------------------");
            }
        }
    }
}

运行结果如下:

...

选书线程:数量455

付账线程:数量455

发货线程:数量455

------------------------------------------

选书线程:数量456

付账线程:数量456

发货线程:数量456

------------------------------------------

...

AutoResetEvent与ManualResetEvent的区别

    他们的用法\声明都很类似,Set方法将信号置为发送状态 Reset方法将信号置为不发送状态WaitOne等待信号的发送。其实,从名字就可以看出一个手动,

    一个自动,这个手动和自动实际指的是在Reset方法的处理上,如下面例子:

    public AutoResetEvent autoevent=new AutoResetEvent(true);

    public ManualResetEvent manualevent=new ManualResetEvent(true);

    默认信号都处于发送状态,

    autoevent.WaitOne();

    manualevent.WaitOne();

    如果某个线程调用上面该方法,则当信号处于发送状态时,该线程会得到信号,得以继续执行。差别就在调用后,autoevent.WaitOne()每次只允许一个线程进入,当某个线程得到信号(也就是有其他线程调用了autoevent.Set()方法后)后,autoevent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待.也就是说,autoevent一次只唤醒一个线程。而manualevent则可以唤醒多个线程,因为当某个线程调用了set方法后,其他调用waitone的线程获得信号得以继续执行,而manualevent不会自动将信号置为不发送.也就是说,除非手工调用了manualevent.Reset().方法,则manualevent将一直保持有信号状态,manualevent也就可以同时唤醒多个线程继续执行。如果上面的程序换成ManualResetEvent的话,就需要在waitone后面做下reset。


[1] http://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent(v=vs.110).aspx

[2] http://www.cnblogs.com/lzjsky/archive/2011/07/11/2102794.html,例子、代码均有改动。

0 条评论

    发表评论

    电子邮件地址不会被公开。 必填项已用 * 标注