這里所說的事件是最基本的控制同步原語,不同于.Net語言中的事件,。在任何時刻,,一個事件可能處于兩種狀態(tài)之一:已觸發(fā)或者未觸發(fā),,如果一個線程在一個未觸發(fā)的事件上面等待,那么只有當(dāng)這個事件的狀態(tài)變成已觸發(fā)時,,這個線程才能繼續(xù)執(zhí)行,;如果在等待時,,事件已經(jīng)處于已觸發(fā)狀態(tài),,那么線程將立即繼續(xù)執(zhí)行。 Windows提供了兩種特殊的事件對象類型來實(shí)現(xiàn)線程之間的合作:自動設(shè)置事件和手動設(shè)置事件,。他們都屬于內(nèi)核對象,。這兩種事件的差別是:當(dāng)AutoResetEvent被觸發(fā)時,只有一個線程可以看到這個信號,,當(dāng)線程看見這個信號時候,,AutoResetEvent會自動切換到未觸發(fā)狀態(tài)。而ManualResetEvent需要手動調(diào)用方法來切換到未觸發(fā)狀態(tài),。如果有多個線程都在等待一個AutoResetEvent的觸發(fā)狀態(tài),,系統(tǒng)將會為這些等待的線程建立一個隊(duì)列,當(dāng)這個AutoResetEvent狀態(tài)切換到觸發(fā)狀態(tài)的時候,,只有一個線程可以看見這個狀態(tài)的變化繼續(xù)執(zhí)行,,其他的線程還必須要等到下一次狀態(tài)切換到已觸發(fā)。我們并不能保證先等待的線程會先繼續(xù)執(zhí)行,,這里面涉及到內(nèi)核線程調(diào)度的一些原因,,比如優(yōu)先級。 AutoResetEvent如果在沒有線程等待的情況下,,切換到已觸發(fā)狀態(tài),,那么以后第一個等待這個事件的線程將可以繼續(xù)執(zhí)行。然而對于ManualResetEvent,, 所有等待的線程在ManualResetEvent設(shè)置成已觸發(fā)狀態(tài)的時候,,都將繼續(xù)執(zhí)行。 一個簡單的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方法,,如果實(shí)參是0的話,則表示查看該AutoResetEvent的狀態(tài),,不會阻塞操作,。 下面是使用AutoResetEvent實(shí)現(xiàn)的BlockingQueue,使用AutoResetEvent的阻塞隊(duì)列效率上要比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(); } //有一個可用項(xiàng),,喚醒一個消費(fèi)者。 _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) 方法,。這是一個原子操作,,表示給第一個參數(shù)_mutex一個信號,,釋放上面的鎖,。然后在第二個參數(shù)上面等待,。
AutoResetEvent和ManualResetEvent這兩種事件都沒有所有者的概念,,任何線程都可以切換事件的狀態(tài),。同樣,他們也沒有遞歸性質(zhì),,不像Mutex和Semaphore,,內(nèi)部有一個計(jì)數(shù)器,。所以多次執(zhí)行Set或Reset方法都沒有任何其他的效果,當(dāng)事件已經(jīng)處于已觸發(fā)狀態(tài)時,多次調(diào)用Set實(shí)際上是被忽略,。這個特性需要我們在開發(fā)程序中特別注意,,往往這個喚醒(Set)會被遺失。比如說有兩個生產(chǎn)者,,前后分別向隊(duì)列中放了一個項(xiàng),。而消費(fèi)者在收到喚醒信號的時候只會去隊(duì)列中拿走一個項(xiàng),。 這兩個事件都會在擁有該事件的應(yīng)用程序域銷毀的時候自動銷毀,。 在 .NET Framework 4中,,當(dāng)?shù)却龝r間預(yù)計(jì)非常短時,并且當(dāng)事件不會跨越進(jìn)程邊界時,,可使用 ManualResetEventSlim 類以獲得更好的性能,。因?yàn)樗锩嬖谀承┑胤绞褂昧俗孕岣吡诵阅堋?/p> 在.NET Framework 4中,,還提供了其他兩個基于ManualResetEventSlim的新類型,,CountdownEvent和ManualResetEventSlim,他們都是使用ManualResetEventSlim來實(shí)現(xiàn)的,。 下面是CountdownEvent的示例,,表示CountdownEvent需要收到3個事件信號才會繼續(xù)執(zhí)行:
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."); }
結(jié)果:
Barrier也有類似的功能,但是它不像CountdownEvent,,CountdownEvent滿足條件之后就一直執(zhí)行下去了,,但Barrier有 下面一個例子就是3個線程都打印0到4,5個數(shù)字,。每個線程每打印一個數(shù)字,,都需要停下來等待其他的線程完成這一輪打印,然后齊頭并進(jìn)打印下面一個數(shù)字,。
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(); } }
測試代碼在這里下載 |
|