線程同步事件等待句柄 react
經過通知操做的方式來保持線程的同步,還能夠方便實現對多個線程的優先級比較的操做函數
本篇繼續介紹WaitHandler類及其子類ManualResetEvent,AutoResetEvent的用法。.NET中線程同步的方式多的讓人看了眼花繚亂,究竟該怎麼去理解呢?其實,咱們拋開.NET環境看線程同步,無非是執行兩種操做:一是互斥/加鎖,目的是保證臨界區代碼操做的「原子性」;另外一種是信號燈操做,目的是保證多個線程按照必定順序執行,如生產者線程要先於消費者線程執行。.NET中線程同步的類無非是對這兩種方式的封裝,目的歸根結底均可以歸結爲實現互斥/加鎖或者是信號燈這兩種方式,只是它們的適用場合有所不一樣。下面咱們根據類的層次結構瞭解WaitHandler及其子類。spa
1.WaitHandler線程
咱們已經知道WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,它封裝Win32同步句柄內核對象,也就是說是這些內核對象的託管版本。code
線程能夠經過調用WaitHandler實例的方法WaitOne在單個等待句柄上阻止。此外,WaitHandler類重載了靜態方法,以等待全部指定的等待句柄都已收集到信號WaitAll,或者等待某一指定的等待句柄收集到信號WaitAny。這些方法都提供了放棄等待的超時間隔、在進入等待以前退出同步上下文的機會,並容許其它線程使用同步上下文。WaitHandler是C#中的抽象類,不能實例化。對象
System.Threading.WaitHandle.WaitOne 使線程一直等待,直到單個事件變爲終止狀態;blog
System.Threading.WaitHandle.WaitAny 阻止線程,直到一個或多個指示的事件變爲終止狀態;繼承
System.Threading.WaitHandle.WaitAll 阻止線程,直到全部指示的事件都變爲終止狀態。遊戲
二、ManualResetEvent和AutoResetEvent事件
區別:ManualResetEvent和AutoResetEvent都繼承自EventWaitHandler,它們的惟一區別就在於父類EventWaitHandler的構造函數參數EventResetMode不一樣,這樣咱們只要弄清了參數EventResetMode值不一樣時,EventWaitHandler類控制線程同步的行爲有什麼不一樣,兩個子類也就清楚了。
1)AutoResetEvent.WaitOne()每次只容許一個線程進入,當某個線程獲得信號後,AutoResetEvent會自動又將信號置爲不發送狀態,則其餘調用WaitOne的線程只有繼續等待,也就是說AutoResetEvent一次只喚醒一個線程;
2)ManualResetEvent則能夠喚醒多個線程,由於當某個線程調用了ManualResetEvent.Set()方法後,其餘調用WaitOne的線程得到信號得以繼續執行,而ManualResetEvent不會自動將信號置爲不發送。
3)也就是說,除非手工調用了ManualResetEvent.Reset()方法,則ManualResetEvent將一直保持有信號狀態,ManualResetEvent也就能夠同時喚醒多個線程繼續執行。
共同點:
1)Set方法將事件狀態設置爲終止狀態,容許一個或多個等待線程繼續;Reset方法將事件狀態設置爲非終止狀態,致使線程阻止;WaitOne阻止當前線程,直到當前線程的WaitHandler收到事件信號。
2)能夠經過構造函數的參數值來決定其初始狀態,若爲true則事件爲終止狀態從而使線程爲非阻塞狀態,爲false則線程爲阻塞狀態。
3)若是某個線程調用WaitOne方法,則當事件狀態爲終止狀態時,該線程會獲得信號,繼續向下執行
三、代碼示例
上邊都是一些理論知識,主要是幫助掌握知識脈絡,咱們不只會用,也得知道爲何這樣用。
假設有這樣的一個場景,主線程開了一個子線程,讓子線程等着,等主線程完成了某件事情時再通知子線程去往下執行,這裏關鍵就在於這個怎麼讓子線程等着,主線程怎通知子線程,通常狀況下咱們不難想到用一個公共變量,因而我們就有了下面的代碼:
(1)設置公共變量實現功能。
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace AutoResetEventTest { class Class1 { static bool flag = true;//設置公共變量 static void DoWork() { Console.WriteLine("worker thread started, now waiting on event..."); while (flag) { } Console.WriteLine("worker thread reactivated, now exiting..."); } static void Main() { Console.WriteLine("main thread starting worker thread..."); Thread t = new Thread(DoWork); t.Start(); Console.WriteLine("main thrad sleeping for 1 second..."); Thread.Sleep(1000); Console.WriteLine("main thread signaling worker thread..."); flag = false; Console.ReadKey(); } } }
(2)AutoResetEvent實現功能
設置公共變量的方式雖然達到了目的,可是看着這代碼就糾結,下面該是咱們的主角上場了,AutoResetEvent 和 ManualResetEvent,這裏以AutoResetEvent爲例,其實不少官方的說法太過於抽象,這裏通俗地講,能夠認爲AutoResetEvent就是一個公共的變量(儘管它是一個事件),建立的時候能夠設置爲false,而後在要等待的線程使用它的WaitOne方法,那麼線程就一直會處於等待狀態,只有這個AutoResetEvent被別的線程使用了Set方法,也就是要發通知的線程使用了它的Set方法,那麼等待的線程就會往下執行了,Set就是發信號,WaitOne是等待信號,只有發了信號,等待的纔會執行。若是不發的話,WaitOne後面的程序就永遠不會執行。好下面看用AutoResetEvent改造上面的程序:
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace AutoResetEventTest { class Class2 { static AutoResetEvent mEvent = new AutoResetEvent(false); //static ManualResetEvent mEvent = new ManualResetEvent(false); static void DoWork() { Console.WriteLine("worker thread started, now waiting on event..."); mEvent.WaitOne(); Console.WriteLine("worker thread reactivated, now exiting..."); } static void Main() { Console.WriteLine("main thread starting worker thread..."); Thread t = new Thread(DoWork); t.Start(); Console.WriteLine("main thrad sleeping for 1 second..."); Thread.Sleep(1000); Console.WriteLine("main thread signaling worker thread..."); mEvent.Set(); Console.ReadKey(); } } }
(3)使用AutoResetEvent使代碼好多了,這裏其實你還會看到,把上面的AutoResetEvent換成ManualResetEvent也是沒有問題的,那麼它兩之間的區別是什麼呢?AutoResetEvent.WaitOne()每次只容許一個線程進入,當某個線程獲得信號後,AutoResetEvent會自動又將信號置爲不發送狀態,則其餘調用WaitOne的線程只有繼續等待,也就是說AutoResetEvent一次只喚醒一個線程;ManualResetEvent則能夠喚醒多個線程,由於當某個線程調用了ManualResetEvent.Set()方法後,其餘調用WaitOne的線程得到信號得以繼續執行,而ManualResetEvent不會自動將信號置爲不發送,也就是說,除非手工調用了ManualResetEvent.Reset()方法,則ManualResetEvent將一直保持有信號狀態,ManualResetEvent也就能夠同時喚醒多個線程繼續執行。
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace AutoResetEventTest { class Class3 { static AutoResetEvent mEvent = new AutoResetEvent(false); //static ManualResetEvent mEvent = new ManualResetEvent(false); static void DoWork() { Console.WriteLine(" worker thread started, now waiting on event..."); for (int i = 0; i < 3; i++) { mEvent.WaitOne(); //mEvent.Reset(); Console.WriteLine(" worker thread reactivated, now exiting..."); } } static void Main() { Console.WriteLine("main thread starting worker thread..."); Thread t = new Thread(DoWork); t.Start(); for (int i = 0; i < 3; i++) { Thread.Sleep(1000); Console.WriteLine("main thread signaling worker thread..."); mEvent.Set(); } } } }
(4)對於(3)中實例,若是你想僅僅把AutoResetEvent換成ManualResetEvent的話,你發現輸出就會亂套了,爲何呢?
假若有autoevent.WaitOne()和manualevent.WaitOne(),當線程獲得信號後都得以繼續執行。差異就在調用後,autoevent.WaitOne()每次只容許一個線程進入,當某個線程獲得信號(也就是有其餘線程調用了autoevent.Set()方法後)後,autoevent會自動又將信號置爲不發送狀態,則其餘調用WaitOne的線程只有繼續等待,也就是說,autoevent一次只喚醒一個線程。而manualevent則能夠喚醒多個線程,當某個線程調用了set方法後,其餘調用waitone的線程得到信號得以繼續執行,而manualevent不會自動將信號置爲不發送,也就是說,除非手工調用了manualevent.Reset()方法,不然manualevent將一直保持有信號狀態,manualevent也就能夠同時喚醒多個線程繼續執行。
在上面代碼中,若是將AutoResetEvent換成ManualResetEvent的話,只要要在waitone後面作下reset,就會達到一樣的效果。
說一個關於AutoResetEvent和ManualResetEvent的笑話
示例場景:張3、李四兩個好朋友去餐館吃飯,兩我的點了一份宮爆雞丁,宮爆雞丁作好須要一段時間,張3、李四不肯傻等,都專心致志的玩起了手機遊戲,心想宮爆雞丁作好了,服務員確定會叫咱們的。服務員上菜以後,張三李四開始享用美味的飯菜,飯菜吃光了,他們再叫服務員過來買單。咱們能夠從這個場景中抽象出來三個線程,張三線程、李四線程和服務員線程,他們之間須要同步:服務員上菜—>張3、李四開始享用宮爆雞丁—>吃好後叫服務員過來買單。這個同步用什麼呢? ManualResetEvent仍是AutoResetEvent?經過上面的分析不難看出,咱們應該用ManualResetEvent進行同步。
編譯後查看運行結果,符合咱們的預期,控制檯輸出爲:
服務員:廚師在作菜呢,兩位稍等...
張三:等着上菜無聊先玩會手機遊戲
李四:等着上菜無聊先玩會手機遊戲
張三:等着上菜無聊先玩會手機遊戲
李四:等着上菜無聊先玩會手機遊戲
服務員:宮爆雞丁好了
張三:開始吃宮爆雞丁
李四:開始吃宮爆雞丁
張三:宮爆雞丁吃光了
李四:宮爆雞丁吃光了
服務員:兩位請買單
若是改用AutoResetEvent進行同步呢?會出現什麼樣的結果?恐怕張三和李四就要打起來了,一個享用了美味的宮爆雞丁,另外一個到要付帳的時候卻還在玩遊戲。