C#多執行緒(6):執行緒通知

痴者工良發表於2020-04-19

回顧一下,前面 lock、Monitor 部分我們學習了執行緒鎖,Mutex 部分學習了程式同步,Semaphor 部分學習了資源池限制。

這一篇將學習 C# 中用於傳送執行緒通知的 AutoRestEvent 類。

AutoRestEvent 類

用於從一個執行緒向另一個執行緒傳送通知。

微軟文件是這樣介紹的:表示執行緒同步事件在一個等待執行緒釋放後收到訊號時自動重置。

其建構函式只有一個:

建構函式裡面的引數用於設定訊號狀態。

建構函式 說明
AutoResetEvent(Boolean) 用一個指示是否將初始狀態設定為終止的布林值初始化 AutoResetEvent 類的新例項。

真糟糕的機器翻譯。

常用方法

AutoRestEvent 類是幹嘛的,建構函式的引數又是幹嘛的?不著急,我們來先來看看這個類常用的方法:

方法 說明
Close() 釋放由當前 WaitHandle 佔用的所有資源。
Reset() 將事件狀態設定為非終止,從而導致執行緒受阻。
Set() 將事件狀態設定為有訊號,從而允許一個或多個等待執行緒繼續執行。
WaitOne() 阻止當前執行緒,直到當前 WaitHandle 收到訊號。
WaitOne(Int32) 阻止當前執行緒,直到當前 WaitHandle 收到訊號,同時使用 32 位帶符號整數指定時間間隔(以毫秒為單位)。
WaitOne(Int32, Boolean) 阻止當前執行緒,直到當前的 WaitHandle 收到訊號為止,同時使用 32 位帶符號整數指定時間間隔,並指定是否在等待之前退出同步域。
WaitOne(TimeSpan) 阻止當前執行緒,直到當前例項收到訊號,同時使用 TimeSpan 指定時間間隔。
WaitOne(TimeSpan, Boolean) 阻止當前執行緒,直到當前例項收到訊號為止,同時使用 TimeSpan 指定時間間隔,並指定是否在等待之前退出同步域。

一個簡單的示例

這裡我們編寫一個這樣的程式:

建立一個執行緒,能夠執行多個階段的任務;每完成一個階段,都需要停下來,等待子執行緒發生通知,才能繼續下一步執行。

.WaitOne() 用來等待另一個執行緒傳送通知;

.Set() 用來對執行緒發出通知,此時 AutoResetEvent 變成終止狀態;

.ReSet() 用來重置 AutoResetEvent 狀態;

    class Program
    {
        // 執行緒通知
        private static AutoResetEvent resetEvent = new AutoResetEvent(false);

        static void Main(string[] args)
        {
            // 建立執行緒
            new Thread(DoOne).Start();

            // 用於不斷向另一個執行緒傳送訊號
            while (true)
            {
                Console.ReadKey();
                resetEvent.Set();           // 發生通知,設定終止狀態
            }
        }

        public static void DoOne()
        {
            Console.WriteLine("等待中,請發出訊號允許我執行");

            // 等待其它執行緒傳送訊號
            resetEvent.WaitOne();

            Console.WriteLine("\n     收到訊號,繼續執行");
            for (int i = 0; i < 5; i++) Thread.Sleep(TimeSpan.FromSeconds(0.5));

            resetEvent.Reset(); // 重置為非終止狀態
            Console.WriteLine("\n第一階段執行完畢,請繼續給予指示");

            // 等待其它執行緒傳送訊號
            resetEvent.WaitOne();
            Console.WriteLine("\n     收到訊號,繼續執行");
            for (int i = 0; i < 5; i++) Thread.Sleep(TimeSpan.FromSeconds(0.5));

            Console.WriteLine("\n第二階段執行完畢,執行緒結束,請手動關閉視窗");
        }
    }

解釋一下

AutoResetEvent 物件有終止和非終止狀態。Set() 設定終止狀態,Reset() 重置非終止狀態。

這個終止狀態,可以理解成訊號已經通知;非終止狀態則是訊號還沒有通知。

注意,注意終止狀態和非終止狀態指的是 AutoResetEvent 的狀態,不是指執行緒的狀態。

執行緒通過呼叫 WaitOne() 方法,等待訊號;
另一個執行緒可以呼叫 Set() 通知 AutoResetEvent 釋放等待執行緒。
然後 AutoResetEvent 變為終止狀態。

需要注意的是,如果 AutoResetEvent 已經處於終止狀態,那麼執行緒呼叫 WaitOne() 不會再起作用。除非呼叫Reset()

建構函式中的引數,正是設定這個狀態的。true 代表終止狀態,false 代表非終止狀態。如果使用 new AutoResetEvent(true); ,則執行緒一開始是無需等待訊號的。

在使用完型別後,您應直接或間接釋放型別,顯式呼叫 Close()/Dispose() 或 使用 using。 當然,也可以直接退出程式。

需要注意的是,如果多次呼叫 Set() 的時間間隔過短,如果第一次 Set() 還沒有結束(訊號傳送需要處理時間),那麼第二次 Set() 可能無效(不起作用)。

複雜一點的示例

我們設計一個程式:

  • Two 執行緒開始處於阻塞狀態;
  • 執行緒 One 可以設定執行緒 Two 繼續執行,然後阻塞自己;
  • 執行緒 Two 可以設定 One 繼續執行,然後阻塞自己;

程式程式碼如下(執行後,請將鍵盤設定成英文輸入狀態再按下按鍵):

    class Program
    {
        // 控制第一個執行緒
        // 第一個執行緒開始時,AutoResetEvent 處於終止狀態,無需等待訊號
        private static AutoResetEvent oneResetEvent = new AutoResetEvent(true);

        // 控制第二個執行緒
        // 第二個執行緒開始時,AutoResetEvent 處於非終止狀態,需要等待訊號
        private static AutoResetEvent twoResetEvent = new AutoResetEvent(false);

        static void Main(string[] args)
        {
            new Thread(DoOne).Start();
            new Thread(DoTwo).Start();

            Console.ReadKey();
        }

        public static void DoOne()
        {
            while (true)
            {
                Console.WriteLine("\n① 按一下鍵,我就讓DoTwo執行");
                Console.ReadKey();
                twoResetEvent.Set();
                oneResetEvent.Reset();
                // 等待 DoTwo() 給我訊號
                oneResetEvent.WaitOne();

                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("\n     DoOne() 執行");
                Console.ForegroundColor = ConsoleColor.White;
            }
        }

        public static void DoTwo()
        {
            while (true)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));

                // 等待 DoOne() 給我訊號
                twoResetEvent.WaitOne();

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("\n     DoTwo() 執行");
                Console.ForegroundColor = ConsoleColor.White;

                Console.WriteLine("\n② 按一下鍵,我就讓DoOne執行");
                Console.ReadKey();
                oneResetEvent.Set();
                twoResetEvent.Reset();
            }
        }
    }

解釋

兩個執行緒具有的功能:阻塞自己、解除另一個執行緒的阻塞。

用電影《最佳拍檔》裡面的一個畫面來理解。

DoOne 、DoTwo 輪流呼吸,不能自己控制自己呼吸,但自己能夠決定別人呼吸。

你搞我,我搞你,就能相互呼吸了。

當然WaitOne() 也可以設定等待時間,如果 光頭佬(DoOne) 耍賴不讓 金剛(DoTwo)呼吸,金剛等待一定時間後,可以強行蕩動天平,落地呼吸。

注意,AutoRestEvent 用得不當容易發生死鎖。
另外 AutoRestEvent 使用的是核心時間模式,因此等待時間不能太長,不然比較耗費 CPU 時間。

AutoResetEvent 也適合用於執行緒同步。

另外,執行緒中使用 WaitOne() ,另一個執行緒使用 Set() 通知後, AutoResetEvent 物件會自動恢復非終止狀態,不需要執行緒使用 Reset()

相關文章