在 .NET 使用 執行緒的同步 Synchronization - ManualResetEvent vs AutoResetEvent
當您在開發多執行應用程式的時候,將會遇到這樣的需求,要能夠在不同的執行緒執行過程中,進行互相的協調、以便同步進行後續的處理工作。在這裡,我們將模擬一個實際的情境,例如,在賽馬場中,將會有五組參賽人員要進行比賽,而要比賽之前,大家需要依序進入到起跑點內,這樣,裁判才能夠鳴槍,讓大家一同起跑;另外,我們還需要能夠下達一個指定,讓這個程式可以結束執行。
面對這樣的需求,我們將會需要用到7個執行緒(其中,有6個背景執行緒,1個前景執行緒)來完成這樣的情境
- 每個參賽人員的執行緒因為會有五組人員要參加比賽,這裡共會產生五個執行緒,在此,使用 ThreadPool.QueueUserWorkItem 產生5個執行緒,並且可以看出,這五個執行緒都是背景執行緒,也就是說,若主執行緒執行完成之後,不論這五個執行緒是否有執行完成,該處理程序也會結束執行的。這個執行緒的委派方法會模擬要進行準備工作,因此,當參賽人員之比賽執行緒開始執行的時候,會模擬等候 2~5 秒鐘的時間。接著,會使用
WaitForGameStart.WaitOne();
方法,等候裁判通知比賽要開始 (在裁判端的執行緒中,會透過WaitForGameStart.Set();
方法,通知這五個執行緒比賽正式開始);當執行緒執行WaitForGameStart.WaitOne();
方法 的時候,該執行將會在 封鎖 (Block) 狀態下,也就是,這個執行緒無法執行任何程式碼。然後,在比賽執行緒將會模擬進行比賽,在這裡比賽的執行緒中,將會模擬休息 5~10 秒鐘,最後,比賽用的執行緒將會結束執行。 - 裁判的執行緒我們將會透過 ThreadPool.QueueUserWorkItem 產生一個背景執行緒,作為裁判下達通知與進行相關動作的執行程式,在這個執行緒中,會使用
ConsoleKeyInfo key = Console.ReadKey();
等候裁判下達指示,若輸入 B 按鍵,便會下達WaitForGameStart.Set();
方法,讓參賽的五個執行緒開始同時來進行比賽;若下達 Q 按鍵,則會執行WaitForExitProgram.Set();
方法,此時,在主執行緒中的最後一行敘述,將不會被封鎖 Block 住,而會繼續執行;因為主執行緒為前景執行緒,當前景執行緒執行完畢後,不論背景執行緒是否有執行完成,整個處理程序將會結束運行。
這個螢幕截圖,將會是該範例程式的執行結果。
底下是上述說明的測試程式碼,若想要了解 AutoResetEvent 與 ManualResetEvent 這兩個類別的差異,可以試著將 WaitForGameStart 變數的型別修改成為 AutoResetEvent,體驗一下執行結果有何差異。
不論是 AutoResetEvent 與 ManualResetEvent 都是提供執行緒間的同步處理工作,該物件內都有初始狀態設定,在我們這個範例中,設定為 fasle,表示尚未收到其他執行緒的為已收到訊號通知,因此,當該執行緒執行到
WaitForGameStart.WaitOne();
敘述的值後,該執行緒將會被凍結,封鎖 Block 在行程式碼上。
此時,您可以想像有個閘門,只要您下令將閘門打開 (在其他執行緒上執行
WaitForGameStart.Set();
方法),這些被封鎖的執行緒將會自動執行;而 AutoResetEvent 與 ManualResetEvent 的差異在於,對於前者若在其他執行緒上執行了 WaitForGameStart.Set();
敘述,該閘門會開啟,但是,只允許一個人通過,並且就會立即關閉起來;而後者,則是打開之後,所有的執行緒都會繼續執行下去,除非我們下達關閉閘門指令。
您可以試著修改
static ManualResetEvent WaitForGameStart = new ManualResetEvent(false);
為 static AutoResetEvent WaitForGameStart = new AutoResetEvent(false)
,接著執行看看差異在哪裡。class Program
{
// 等候通知便結束程式執行
static AutoResetEvent WaitForExitProgram = new AutoResetEvent(false);
// 等候通知,便開始進行比賽
static ManualResetEvent WaitForGameStart = new ManualResetEvent(false);
// 定義隨機亂數用於測試之用
static Random 隨機亂數 = new Random();
static void Main()
{
// 使用 ThreadPool.QueueUserWorkItem 產生5個執行緒
Console.WriteLine("開始比賽前的比賽準備");
for (int i = 1; i <= 5; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(進行比賽), i);
}
Console.WriteLine("等候裁判通知,比賽就會開始");
ThreadPool.QueueUserWorkItem((x) =>
{
while (true)
{
ConsoleKeyInfo key = Console.ReadKey();
if (key.Key == ConsoleKey.B)
{
WaitForGameStart.Set();
}
if (key.Key == ConsoleKey.Q)
{
WaitForExitProgram.Set();
}
}
});
Console.WriteLine("等候通知,該程式就會結束執行");
WaitForExitProgram.WaitOne();
}
static void 進行比賽(Object state)
{
// 向等候的執行緒通知發生事件
int are = (int)state;
int time = 1000 * 隨機亂數.Next(2, 5);
Console.WriteLine($"參賽者 {are} 需要 {time} 毫秒的時間來準備");
Thread.Sleep(time);
Console.WriteLine($"參賽者 {are} 準備好了");
// 設定作業已經成功
WaitForGameStart.WaitOne();
Console.WriteLine($"參賽者 {are} 開始進行比賽");
time = 1000 * 隨機亂數.Next(5, 10);
Thread.Sleep(time);
Console.WriteLine($"參賽者 {are} 抵達終點");
}
}
關於 Xamarin 在台灣的學習技術資源
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程