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.
 */
上一篇:C#线程调度AutoResetEvent和ManualResetEvent区别


下一篇:while循环&CPU占用100%的解决方法