2018年8月25日 星期六

在 .NET 使用 執行緒的同步 Synchronization - ManualResetEvent vs AutoResetEvent

在 .NET 使用 執行緒的同步 Synchronization - ManualResetEvent vs AutoResetEvent

當您在開發多執行應用程式的時候,將會遇到這樣的需求,要能夠在不同的執行緒執行過程中,進行互相的協調、以便同步進行後續的處理工作。在這裡,我們將模擬一個實際的情境,例如,在賽馬場中,將會有五組參賽人員要進行比賽,而要比賽之前,大家需要依序進入到起跑點內,這樣,裁判才能夠鳴槍,讓大家一同起跑;另外,我們還需要能夠下達一個指定,讓這個程式可以結束執行。
這篇文章的範例程式碼,可以從 https://github.com/vulcanlee/CSharpNotes2018/tree/master/ManualResetEventDemo 取得
面對這樣的需求,我們將會需要用到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 住,而會繼續執行;因為主執行緒為前景執行緒,當前景執行緒執行完畢後,不論背景執行緒是否有執行完成,整個處理程序將會結束運行。
這個螢幕截圖,將會是該範例程式的執行結果。
ManualResetEvent AutoResetEvent
底下是上述說明的測試程式碼,若想要了解 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),接著執行看看差異在哪裡。
C Sharp / C#
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 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程



沒有留言:

張貼留言