天天看點

C# 應用 - 多線程 6) 處理同步資料之手動同步 AutoResetEvent 和 ManualResetEvent

1. 類的關系

  1. AutoResetEvent 和 ManualResetEvent 都繼承自 System.Threading.EventWaitHandle 類(EventWaitHandle 繼承自 WaitHandle);
  2. 用于線程互動 (或線程信号)

2. 常用方法

2.1 WaitHandle 幾個常用的方法

  1. public virtual bool WaitOne(int millisecondsTimeout); //阻止目前線程 ,直到目前 System.Threading.WaitHandle 收到信号,或直到 millisecondsTimeout 後。
  2. public virtual bool WaitOne(); //阻止目前線程,直到目前 System.Threading.WaitHandle 收到信号。
  3. public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout);
  4. public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout);

2.2 EventWaitHandle 幾個常用的方法

  1. public bool Set();

    将事件設定為有信号,進而允許一個或多個等待線程繼續執行

  2. public bool Reset();

    将事件設定為非終止狀态,進而導緻線程受阻

2.3 AutoResetEvent

  1. 該類的定義:通知正在等待的線程已發生事件
  2. public AutoResetEvent(bool initialState); //initialState 指是否将初始狀态設定為終止狀态的類

2.4 ManualResetEvent

  1. 該類的定義:通知一個或多個正在等待的線程已發生事件
  2. public ManualResetEvent(bool initialState);

3. 使用邏輯

3.1 AutoResetEvent 的使用邏輯

  1. 執行個體化 AutoResetEvent 時,需要設定初始狀态是否為終止狀态;
  2. 若遇到狀态為非終止狀态的 AutoResetEvent 的 Wait 系列方法(如 WaitOne()),将 阻止目前線程繼續往下執行,直到目前 AutoResetEvent 收到信号;
  3. 收到信号後,保持終止狀态并釋放一個等待線程,讓其繼續往下執行,然後自動傳回非終止狀态。

3.2 ManualResetEvent 的使用邏輯

  1. 執行個體化 ManualResetEvent 時,需要設定初始狀态是否為終止狀态;
  2. 若遇到狀态為非終止狀态的 ManualResetEvent 的 Wait 系列方法(如 WaitOne()),将 阻止目前線程繼續往下執行,直到目前 ManualResetEvent 收到信号;
  3. 收到信号後,所有等待線程都被釋放,可以繼續往下執行;
  4. 跟 AutoResetEvent 不同的是,收到信号後,ManualResetEvent 将繼續保持信号,直到通過手動調用 Reset() 方法重置 ManualResetEvent 的狀态為非終止。

4. 例子

4.1 AutoResetEvent

using System;
using System.Threading;

class Example
{
    private static AutoResetEvent event_1 = new AutoResetEvent(true);
    private static AutoResetEvent event_2 = new AutoResetEvent(false);

    static void Main()
    {
        for (int i = 1; i < 4; i++)
        {
            Thread t = new Thread(ThreadProc);
            t.Name = "Thread_" + i;
            t.Start();
        }
        Thread.Sleep(250);

        for (int i = 0; i < 2; i++)
        {
            Console.WriteLine("按任意鍵釋放另一個線程");
            Console.ReadLine();
            event_1.Set();
            Thread.Sleep(250);
        }

        Console.WriteLine("\r\n至此所有線程都在等待 AutoResetEvent #2.");
        
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine("按任意鍵釋放另一個線程");
            Console.ReadLine();
            event_2.Set();
            Thread.Sleep(250);
        }
    }

    static void ThreadProc()
    {
        string name = Thread.CurrentThread.Name;

        Console.WriteLine("{0} 等待 AutoResetEvent #1.", name);
        event_1.WaitOne();
        Console.WriteLine("{0} 被 AutoResetEvent #1 釋放.", name);

        Console.WriteLine("{0} 等待 AutoResetEvent #2.", name);
        event_2.WaitOne();
        Console.WriteLine("{0} 被 from AutoResetEvent #2 釋放.", name);

        Console.WriteLine("{0} 結束.", name);
    }
}

/* 上面例子将輸出如下:

Thread_1 等待 AutoResetEvent #1.
Thread_1 被 AutoResetEvent #1 釋放.
Thread_1 等待 AutoResetEvent #2.
Thread_2 等待 AutoResetEvent #1.
Thread_3 等待 AutoResetEvent #1.
按任意鍵釋放另一個線程

Thread_2 被 AutoResetEvent #1 釋放.
Thread_2 等待 AutoResetEvent #2.
按任意鍵釋放另一個線程

Thread_3 被 AutoResetEvent #1 釋放.
Thread_3 等待 AutoResetEvent #2.

至此所有線程都在等待 AutoResetEvent #2.
按任意鍵釋放另一個線程.

Thread_1 被 from AutoResetEvent #2 釋放.
Thread_1 結束.
按任意鍵釋放另一個線程.

Thread_2 被 from AutoResetEvent #2 釋放.
Thread_2 結束.
按任意鍵釋放另一個線程.

Thread_3 被 from AutoResetEvent #2 釋放.
Thread_3 結束.
 */
           

4.2 ManualResetEvent

using System;
using System.Threading;

public class Example
{
    private static ManualResetEvent mre = new ManualResetEvent(false);

    static void Main()
    {
        Console.WriteLine("\n開啟 3 個被 ManualResetEvent 阻塞的線程\n");
        
        for (int i = 0; i <= 2; i++)
        {
            Thread t = new Thread(ThreadProc);
            t.Name = "Thread_" + i;
            t.Start();
        }
        
        Thread.Sleep(500);
        Console.WriteLine("\n所有線程已開啟,請按下 enter 鍵發送信号進而一次性釋放所有線程\n");
        Console.ReadLine();
        
        mre.Set();
        
        Thread.Sleep(500); // 主線程等待 500 毫秒讓子線程先執行完畢
        
        Console.WriteLine("\n當 ManualResetEvent 是有信号時,即使遇到 WaitOne() 也不會被阻塞:\n");
        
        for (int i = 3; i <= 4; i++)
        {
            Thread t = new Thread(ThreadProc);
            t.Name = "Thread_" + i;
            t.Start();
        }
        
        Thread.Sleep(500);
        Console.WriteLine("\n請按下 enter 鍵盤觸發 Reset() 方法, 進而後面的線程在遇到 WaitOne() 時繼續被阻塞.\n");
        Console.ReadLine();
        
        mre.Reset();
        
        Thread t5 = new Thread(ThreadProc);
        t5.Name = "Thread_5";
        t5.Start();
        
        Thread.Sleep(500);
        Console.WriteLine("\n請按下 enter 鍵發送信号.");
        Console.ReadLine();
        
        mre.Set();
        
        Console.ReadLine();
    }

    private static void ThreadProc()
    {
        string name = Thread.CurrentThread.Name;

        Console.WriteLine(name + " starts and calls mre.WaitOne()");

        mre.WaitOne();

        Console.WriteLine(name + " ends.");
    }
}

/* 上面例子将輸出如下:

開啟 3 個被 ManualResetEvent 阻塞的線程

Thread_0 starts and calls mre.WaitOne()
Thread_1 starts and calls mre.WaitOne()
Thread_2 starts and calls mre.WaitOne()

所有線程已開啟,請按下 enter 鍵發送信号進而一次性釋放所有線程


Thread_0 ends.
Thread_1 ends.
Thread_2 ends.

當 ManualResetEvent 是有信号時,即使遇到 WaitOne() 也不會被阻塞:

Thread_3 starts and calls mre.WaitOne()
Thread_3 ends.
Thread_4 starts and calls mre.WaitOne()
Thread_4 ends.

請按下 enter 鍵盤觸發 Reset() 方法, 進而後面的線程在遇到 WaitOne() 時繼續被阻塞.


Thread_5 starts and calls mre.WaitOne()

請按下 enter 鍵發送信号.

Thread_5 ends.
 */