前幾天一朋友問我如何實現線程的順序執行,說真的,雖然看過CLR這本書,也把線程部分拜讀了兩遍,但是這個問題出來之后還是沒有一個思路。今天在搜索資料的時候無意中再次看到AutoResetEvent這個東西,當然我知道它是和線程有關,用於處理線程切換之類的(可能在測試Demo之前理解有誤),於是決定用AutoResetEvent來處理上面的問題。
這里以園區一個園友的例子來說明,這個例子就是 買書--》付款--》拿書這個過程,該過程會持續n(通過變量設置)次,並且每一次都要按照順序執行,有可能有同學會疑問,直接Sleep不就好了,干嘛非要多個線程,如果處理某一個環節的時間過久或者是業務復雜,那么整個程序就直接未響應了,所以這里加入多線程來保證程序的響應。
class Program { //循環次數 const int numIterations = 10; //買書 static AutoResetEvent buyResetEvent = new AutoResetEvent(false); //付款 static AutoResetEvent payResetEvent = new AutoResetEvent(false); //取書 static AutoResetEvent getBookEvent = new AutoResetEvent(false); //循環的次數 static int number; static void Main(string[] args) { //付款線程 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; //允許付款線程等待 payResetEvent.Set(); //禁止買書線程等待 buyResetEvent.WaitOne(); } Console.Read(); } static void PayMoneyProc() { while (true) { //等待付款 payResetEvent.WaitOne(); Console.WriteLine("{0}:數量{1}", Thread.CurrentThread.Name, number); //允許取書線程等待 getBookEvent.Set(); } } static void GetBookProc() { while (true) { //等待付款 getBookEvent.WaitOne(); Console.WriteLine("{0}:數量{1}", Thread.CurrentThread.Name, number); Console.WriteLine("------------------------------------------"); //允許買書線程等待(到這一步一個完整的買書流程就執行結束了,完全按照買書--》付款--》取書的流程) buyResetEvent.Set(); } } }
上述代碼不復雜,但是有幾個關鍵的對象和方法,接下來詳細進行說明。
1.分別定義了買書、付款、拿書三個AutoResetEvent,注意定義的時候傳入了false,這也AutoSet默認是不可用的,需要手動調用Set方法才可以呢。該對象是決定是否能WaitOne一個請求的關鍵,當AutoResetEvent.Set執行則可以使用AutoResetEvent.WaitOne進行一個等待請求,如果再有WaitOne請求,則要繼續等待Set的執行;
2.先看for循環,在循環中我們使用payResetEvent.Set()這句代碼,這也PayThread中的WaitOne請求就可以獲得批准(下文有介紹),同時我們又加上了buyResetEvent.WaitOne()(這樣在上一次購買流程結束之前,新的一次流程是不可以執行的,這也就是我們的最大問題,保證線程按照順序執行);
3.定義了付款和拿書的Thread,並且在方法內都是While循環,該循環主要是為了可以將我們的過程進行多次,畢竟線程只會執行一遍嘛。當然這個不是重點,重點是While中我們的操作,先看PayThrea的操作,先調用payResetEvent.WaitOne()請求一個等待操作,當然可以立馬執行,因為在 2 中我們說了for中是調用了payResetEvent.Set()操作,這樣就可以直接得到一個請求響應,輸出關鍵信息,然后又到了重點,我們調用了getBookEvent.Set(),這是為什么呢,因為付款不成功是不可以拿書走的啊,那樣就是偷盜了。注意、注意、注意,重要的話說三遍,GetBookThread操作中也有一個waitOne請求,可是為什么不會執行呢,因為沒有Set呢,我們初始化AutoResetEvent的時候我們設置的是false,這樣默認就不可以得到一個請求,必須手動調用Set才可以,由於在PayMoneyThread的最后一行代碼中我們調用了getBookEvent.Set(),這樣getBookEvent.WaitOne就可用了。在GetBookThread中依次輸出關鍵信息,最后一行代碼又來了,再次調用了一個AutoResetEvent.Set(),沒錯就是買書的對象,因為到了這一步完整的買書流程就結束了,可以再次買書了啊啊啊啊啊啊,可以買書了,好開心啊。然后再次回到for循環中的最后一行代碼,buyResetEvent.Wait()此時就復活了,開始第好幾次的買書流程。
運行截圖如下(絕對真實,毫無PS):
好了,退朝,有事改天上朝再議。
Update:鑒於大家對這個使用方式有不同的建議,另外.net的版本都4.6了,所以重新使用Task的方式進行了更新,歡迎繼續拍磚.
static void Main(string[] args) { for (int i = 0; i < 10; i++) { Task buyTask = Task.Factory.StartNew(() => { BuyBook(); }).ContinueWith((state) => { PayMoney(); }).ContinueWith((state) => { TakeBook(); }); Task.WaitAll(buyTask); Console.WriteLine(); } Console.Read(); } private static void BuyBook() { Console.WriteLine("書太多了,挑花眼了"); } private static void PayMoney() { Console.WriteLine("先付錢才能取書哦"); } private static void TakeBook() { Console.WriteLine("取書嘍"); }