打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
.Net CLR 中的同步机制(三): AutoResetEvent和ManualResetEvent

这里所说的事件是最基本的控制同步原语,不同于.Net语言中的事件。在任何时刻,一个事件可能处于两种状态之一:已触发或者未触发,如果一个线程在一个未触发的事件上面等待,那么只有当这个事件的状态变成已触发时,这个线程才能继续执行;如果在等待时,事件已经处于已触发状态,那么线程将立即继续执行。

Windows提供了两种特殊的事件对象类型来实现线程之间的合作:自动设置事件和手动设置事件。他们都属于内核对象。这两种事件的差别是:当AutoResetEvent被触发时,只有一个线程可以看到这个信号,当线程看见这个信号时候,AutoResetEvent会自动切换到未触发状态。而ManualResetEvent需要手动调用方法来切换到未触发状态。如果有多个线程都在等待一个AutoResetEvent的触发状态,系统将会为这些等待的线程建立一个队列,当这个AutoResetEvent状态切换到触发状态的时候,只有一个线程可以看见这个状态的变化继续执行,其他的线程还必须要等到下一次状态切换到已触发。我们并不能保证先等待的线程会先继续执行,这里面涉及到内核线程调度的一些原因,比如优先级。 AutoResetEvent如果在没有线程等待的情况下,切换到已触发状态,那么以后第一个等待这个事件的线程将可以继续执行。然而对于ManualResetEvent, 所有等待的线程在ManualResetEvent设置成已触发状态的时候,都将继续执行。

一个简单的AutoResetEvent示例:

 1 class Program  2     {  3         static AutoResetEvent are = new AutoResetEvent(false); 4  5         static void Main()  6         {  7             new Thread(Waiter).Start();  8             Thread.Sleep(1000);               9             are.Set();10 11             Console.ReadLine(); 12         }13 14         static void Waiter() 15         { 16             Console.WriteLine("Waiting..."); 17             are.WaitOne();                18             Console.WriteLine("Notified"); 19         } 20     }

 

值得一提的是,AutoResetEvent的WaitOne方法,如果实参是0的话,则表示查看该AutoResetEvent的状态,不会阻塞操作。

下面是使用AutoResetEvent实现的BlockingQueue,使用AutoResetEvent的阻塞队列效率上要比Monitor和4.0的BlockingCollection差很多。

public class BlockingQueueWithEvent<T>    {        private Queue<T> _queue = new Queue<T>();        private Mutex _mutex = new Mutex();        private AutoResetEvent _event = new AutoResetEvent(false);        public void Enqueue(T obj)        {            _mutex.WaitOne();            try            {                _queue.Enqueue(obj);            }            finally             {                _mutex.ReleaseMutex();            }            //有一个可用项,唤醒一个消费者。            _event.Set();        }        public T Dequeue()        {            T obj = default(T);            bool taken = true;            _mutex.WaitOne();            try            {                while (_queue.Count == 0)                {                    taken = false;                    WaitHandle.SignalAndWait(_mutex, _event);                    _mutex.WaitOne();                    taken = true;                }                obj = _queue.Dequeue();            }            finally            {                if (taken)                {                    _mutex.ReleaseMutex();                }            }            return obj;        }    }

代码中使用到了 WaitHandle.SignalAndWait(_mutex, _event) 方法。这是一个原子操作,表示给第一个参数_mutex一个信号,释放上面的锁。然后在第二个参数上面等待。

 

AutoResetEvent和ManualResetEvent这两种事件都没有所有者的概念,任何线程都可以切换事件的状态。同样,他们也没有递归性质,不像Mutex和Semaphore,内部有一个计数器。所以多次执行Set或Reset方法都没有任何其他的效果,当事件已经处于已触发状态时,多次调用Set实际上是被忽略。这个特性需要我们在开发程序中特别注意,往往这个唤醒(Set)会被遗失。比如说有两个生产者,前后分别向队列中放了一个项。而消费者在收到唤醒信号的时候只会去队列中拿走一个项。

这两个事件都会在拥有该事件的应用程序域销毁的时候自动销毁。

在 .NET Framework 4中,当等待时间预计非常短时,并且当事件不会跨越进程边界时,可使用 ManualResetEventSlim 类以获得更好的性能。因为它里面在某些地方使用了自旋,提高了性能。

在.NET Framework 4中,还提供了其他两个基于ManualResetEventSlim的新类型,CountdownEvent和ManualResetEventSlim,他们都是使用ManualResetEventSlim来实现的。

下面是CountdownEvent的示例,表示CountdownEvent需要收到3个事件信号才会继续执行:

 

static CountdownEvent cde = new CountdownEvent(3);static void TestCountDownEvent()        {            Task.Factory.StartNew(() =>                {                    Thread.Sleep(1000);                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);                    cde.Signal();                });            Task.Factory.StartNew(() =>            {                Thread.Sleep(1000);                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);                cde.Signal();            });            Task.Factory.StartNew(() =>            {                Thread.Sleep(1000);                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);                cde.Signal();            });            cde.Wait();            Console.WriteLine("all are finished.");        }

 

结果:

 

Barrier也有类似的功能,但是它不像CountdownEvent,CountdownEvent满足条件之后就一直执行下去了,但Barrier有SignalAndWait,信号以后还继续等待。有一种“步骤”的感觉。因为一张图:

下面一个例子就是3个线程都打印0到4,5个数字。每个线程每打印一个数字,都需要停下来等待其他的线程完成这一轮打印,然后齐头并进打印下面一个数字。

 

        static void Main()        {            TestBarrier();            Console.ReadLine();        }        static Barrier b = new Barrier(3);        static void TestBarrier()        {            Task.Factory.StartNew(TestBarrierMethod);            Task.Factory.StartNew(TestBarrierMethod);            Task.Factory.StartNew(TestBarrierMethod);        }        private static void TestBarrierMethod()        {            for (int i = 0; i < 5; i++)            {                Console.Write(i + " ");                b.SignalAndWait();            }        }

 

 

 

测试代码在这里下载

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C#多线程:深入了解线程同步lock,Monitor,Mutex,同步事件和等待句柄(中)...
C #中的几个线程同步对象方法
线程控制Event
AutoResetEvent与ManualResetEvent区别
AutoResetEvent和ManualResetEvent
C#关于AutoResetEvent的使用介绍[转载]
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服