ManualResetEvent 是 C# 中的一个线程同步工具,用于线程间的信号传递。它通常用于控制线程的执行顺序,或者在多线程环境下进行某些操作的协调。它是 System.Threading 命名空间中的一个类,继承自 EventWaitHandle 类。

什么是 ManualResetEvent

ManualResetEvent 是一种手动重置的事件对象。线程可以通过调用 Set() 方法来“触发”事件,通知一个或多个等待线程继续执行。当事件被触发时,等待的线程会被唤醒继续执行,直到调用 Reset() 方法将事件重置为非信号状态。

如何使用 ManualResetEvent

  1. Set():将事件设为有信号的状态,所有等待的线程会被唤醒。
  2. Reset():将事件设为无信号的状态,等待线程会被挂起,直到事件被重新设置为有信号状态。
  3. WaitOne():等待事件变为有信号的状态。如果事件已经有信号,线程将继续执行。如果没有信号,线程会阻塞,直到事件有信号。

使用场景

  • 线程同步:你可以让多个线程在某个特定时刻开始工作。
  • 线程间的信号传递:用来控制一个线程是否开始执行或继续执行,通常用来模拟信号量或线程间的条件变量。

代码示例:

using System;
using System.Threading;

class Program
{
    static ManualResetEvent manualResetEvent = new ManualResetEvent(false); // 初始状态为无信号(false)

    static void Main()
    {
        // 启动多个线程
        Thread t1 = new Thread(Worker);
        Thread t2 = new Thread(Worker);

        t1.Start();
        t2.Start();

        Console.WriteLine("Press Enter to signal threads to start...");
        Console.ReadLine();

        // 通过 Set() 触发事件,允许线程继续执行
        manualResetEvent.Set();

        Console.WriteLine("Threads have been signaled to start.");
        
        // 等待线程完成
        t1.Join();
        t2.Join();

        Console.WriteLine("All threads have completed.");
    }

    static void Worker()
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " is waiting for signal...");
        manualResetEvent.WaitOne(); // 阻塞,直到事件被 Set()

        // 模拟工作
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " has started working.");
        Thread.Sleep(2000); // 模拟一些工作
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " has finished working.");
    }
}

代码解释:

  1. 初始化 ManualResetEventmanualResetEvent 被初始化为 false,表示开始时事件没有信号,线程会被阻塞等待。
  2. 创建线程t1 和 t2 是两个线程,它们都会调用 Worker 方法。
  3. WaitOne():在每个线程中,我们使用 manualResetEvent.WaitOne() 来让线程阻塞,直到事件变为有信号的状态。
  4. Set():当按下 Enter 键后,manualResetEvent.Set() 会将事件设为有信号的状态,唤醒所有等待的线程。
  5. 线程工作:一旦线程被唤醒,它们将继续执行并模拟工作。
  6. Join():通过调用 t1.Join() 和 t2.Join() 等待线程完成,确保主线程在所有子线程完成后退出。

重要方法:

  • ManualResetEvent(bool initialState):构造函数,指定事件的初始状态。如果为 true,事件立即为有信号状态;如果为 false,事件为无信号状态。
  • Set():设置事件为有信号状态,唤醒所有正在等待的线程。
  • Reset():将事件状态重置为无信号状态,阻止等待的线程继续执行,直到再次调用 Set()
  • WaitOne():让调用线程等待,直到事件状态为有信号状态。如果事件已经有信号,线程继续执行;否则,线程会阻塞。

注意事项:

  • 手动重置ManualResetEvent 是手动重置的。即一旦事件被设置为有信号状态,它会保持该状态直到显式调用 Reset()。这意味着它不会像 AutoResetEvent 一样在唤醒一个线程后自动重置为无信号状态。
  • 多线程操作ManualResetEvent 适用于多个线程需要同时等待一个事件的场景,所有等待线程都会被唤醒。
  • 性能考虑:虽然 ManualResetEvent 是一个有效的同步机制,但在某些高频繁的同步需求中,可能会影响性能。要根据实际需求选择适当的同步工具。

AutoResetEvent vs. ManualResetEvent:

  • AutoResetEvent:在每次唤醒一个线程后,自动将事件重置为无信号状态。
  • ManualResetEvent:必须显式调用 Reset() 来将事件重置为无信号状态,适合多个线程都等待同一个事件的场景。

总结:

ManualResetEvent 是一个非常有用的同步工具,适用于多个线程等待一个事件的场景。通过 Set()Reset()和 WaitOne(),你可以控制线程的执行顺序和协作。