Wait() 和 Pulse() 機制用於線程間交互。當在一個對象上使用Wait() 方法時,訪問這個對象的線程就會一直等待直到被喚醒。Pulse() 和 PulseAll() 方法用來通知等待的線程醒來的。下面是關於Wait() 和 Pulse() 方法如何運行的例子,WaitAndPulse.cs:
Wait() 和 Pulse() 方法僅可以在Enter() 和 Exit() 代碼塊內部調用。
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:Daniel Dong * Blog: www.cnblogs.com/danielWise * Email: guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace WaitAndPulse { public class LockMe { } class WaitPulse1 { private int result = 0; private LockMe lM; public WaitPulse1() { } public WaitPulse1(LockMe l) { this.lM = l; } public void CriticalSection() { Monitor.Enter(this.lM); //Enter the Critical Section Console.WriteLine("WaitPulse1: Entered Thread " + Thread.CurrentThread.GetHashCode()); for (int i = 1; i <= 5; i++) { Monitor.Wait(this.lM); Console.WriteLine("WaitPulse1: WokeUp"); Console.WriteLine("WaitPulse1: Result = " + result++ + " ThreadID " + Thread.CurrentThread.GetHashCode()); Monitor.Pulse(this.lM); } Console.WriteLine("WaitPulse1: Exiting Thread " + Thread.CurrentThread.GetHashCode()); //Exit the Critical Section Monitor.Exit(this.lM); } } class WaitPulse2 { private int result = 0; private LockMe lM; public WaitPulse2() { } public WaitPulse2(LockMe l) { this.lM = l; } public void CriticalSection() { Monitor.Enter(this.lM); //Enter the Critical Section Console.WriteLine("WaitPulse2: Entered Thread " + Thread.CurrentThread.GetHashCode()); for (int i = 1; i <= 5; i++) { Monitor.Pulse(this.lM); Console.WriteLine("WaitPulse2: Result = " + result++ + " ThreadID " + Thread.CurrentThread.GetHashCode()); Monitor.Wait(this.lM); Console.WriteLine("WaitPulse2: WokeUp"); } Console.WriteLine("WaitPulse2: Exiting Thread " + Thread.CurrentThread.GetHashCode()); //Exit the Critical Section Monitor.Exit(this.lM); } } public class ClassForMain { public static void Main(string[] args) { LockMe l = new LockMe(); WaitPulse1 e1 = new WaitPulse1(l); WaitPulse2 e2 = new WaitPulse2(l); Thread t1 = new Thread(new ThreadStart(e1.CriticalSection)); t1.Start(); Thread t2 = new Thread(new ThreadStart(e2.CriticalSection)); t2.Start(); //Wait till the user enters something Console.ReadLine(); } } }
輸出結果如下:
在Main() 方法中,我們創建了一個LockMe對象。然后創建了兩個對象,WaitPulse1, WaitPulse2, 接着把它們委托給線程以便於線程可以調用這兩個對象的CriticalSection()方法。注意WaitPulse1和WaitPulse2這兩個對象中的LockMe實例是不同的,因為傳遞給對應構造函數的對象引用不同。初始化完對象以后,我們創建了兩個線程t1 和 t2, 並向這兩個線程分別傳遞各自的CriticalSection()函數。
假設WaitPulse1.CriticalSection() 先執行,線程t1 進入方法的關鍵部分並在鎖住LockMe對象后在for循環中執行Monitor.Wait()。由於執行了Monitor.Wait(), 所以它得等待其他線程調用Monitor.Pulse()方法(一個運行時通知)來將其喚醒。我們鎖住LockMe對象是因為我們只希望在任意時間僅有一個對象訪問共享LockMe實例。
注意當線程執行Monitor.Wait()方法時,它會暫時釋放LockMe對象上的鎖,這樣其他線程就可以訪問LockMe對象。在線程t1進入等待狀態后,線程t2可以自由地訪問LockMe對象。盡管這兩個線程都有自己的LockMe對象(WaitPulse1, WaitPulse2),但是它們都引用同一個對象。線程t2獲得LockMe對象上的鎖並進入WaitPulse2.CriticalSection()方法。當它進入for循環時,它給等待LockMe對象的線程(本例中是t1)發送一個運行時通知(Monitor.Pulse())然后進入等待狀態。
最終,t1醒來並獲得LockMe對象的鎖。線程t1然后訪問result變量並向等待LockMe對象的線程(本例中為t2)發送一個運行時通知。如此反復直到for循環結束。
如果你依據程序的輸出結果來看上面的描述,那么我們說的概念會非常清晰易懂。要注意每個Enter()方法都有一個Exit()方法匹配否則程序不應該結束。
Enter()方法接受一個對象作為參數。如果參數為null 或者參數時一個方法名或者一個值類型的對象(比如int型),Enter()方法都會拋出異常。
下一篇將會介紹Monitor.TryEnter() 方法 和 Lock 語句…