C#多線程系列之線程通知

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() 。

到此這篇關於C#多線程系列之線程通知的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: