You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Monitor.Wait和Pulse实现WaitHandle.WaitAll?求可行示例

问题描述

我在阅读Joel Albahari的线程相关内容时,他提到:

在简单场景中,模拟适用于一组等待句柄的静态方法很容易。调用WaitAll的等效操作不过是一个包含所有替代等待句柄所用标志的阻塞条件:

lock (_locker)
  while (!_flag1 && !_flag2 && !_flag3...)
    Monitor.Wait (_locker);

但我并不理解如何像实现AutoResetEvent和ManualResetEvent(例如ManualResetEventSlim的实现方式)那样,用Monitor.Wait和Monitor.Pulse实现可正常工作的WaitHandle.WaitAll替代方案。请问有人能提供可行的示例实现吗?


实现示例

我们可以先基于Monitor实现自定义的自动/手动重置事件,再在此基础上实现WaitAll的逻辑。

1. 自定义重置事件类

先实现一个支持自动/手动重置的基础事件类,模仿ManualResetEventSlim和AutoResetEvent的核心行为:

public class CustomResetEvent
{
    private readonly object _locker = new object();
    private bool _isSignaled;
    private readonly bool _autoReset;
    private readonly List<object> _sharedWaiters = new List<object>();

    public CustomResetEvent(bool initialState, bool autoReset)
    {
        _isSignaled = initialState;
        _autoReset = autoReset;
    }

    // 注册共享等待锁,用于WaitAll时的唤醒通知
    internal void RegisterSharedWaiter(object sharedLocker)
    {
        lock (_locker)
        {
            if (!_sharedWaiters.Contains(sharedLocker))
                _sharedWaiters.Add(sharedLocker);
        }
    }

    public void Set()
    {
        lock (_locker)
        {
            if (!_isSignaled)
            {
                _isSignaled = true;
                Monitor.PulseAll(_locker); // 唤醒当前事件的等待线程
                // 同时唤醒所有注册的共享等待锁(用于WaitAll)
                foreach (var waiter in _sharedWaiters)
                {
                    lock (waiter) Monitor.PulseAll(waiter);
                }
            }
        }
    }

    public void Reset()
    {
        lock (_locker)
        {
            _isSignaled = false;
        }
    }

    public bool Wait(TimeSpan timeout)
    {
        lock (_locker)
        {
            if (_isSignaled)
            {
                if (_autoReset) _isSignaled = false;
                return true;
            }
            return Monitor.Wait(_locker, timeout);
        }
    }

    public void Wait() => Wait(Timeout.InfiniteTimeSpan);

    internal bool IsSignaled
    {
        get { lock (_locker) return _isSignaled; }
    }

    internal bool AutoReset => _autoReset;
}

2. 实现WaitAll替代方法

基于自定义重置事件,实现类似WaitHandle.WaitAll的功能:等待所有传入事件都被触发,自动重置事件触发后会自动恢复未信号状态。

public static class CustomWaitHandle
{
    public static void WaitAll(params CustomResetEvent[] events)
    {
        if (events == null || events.Length == 0)
            throw new ArgumentException("至少需要传入一个事件");

        var sharedLocker = new object();
        // 给每个事件注册共享等待锁,确保事件触发时能唤醒WaitAll的等待线程
        foreach (var evt in events)
        {
            evt.RegisterSharedWaiter(sharedLocker);
        }

        lock (sharedLocker)
        {
            bool allSignaled;
            do
            {
                allSignaled = true;
                // 检查所有事件是否都处于信号状态
                foreach (var evt in events)
                {
                    if (!evt.IsSignaled)
                    {
                        allSignaled = false;
                        break;
                    }
                }

                if (!allSignaled)
                {
                    Monitor.Wait(sharedLocker);
                }
                else
                {
                    // 自动重置事件触发后重置状态
                    foreach (var evt in events)
                    {
                        if (evt.AutoReset) evt.Reset();
                    }
                }
            } while (!allSignaled);
        }
    }
}

3. 使用示例

class Program
{
    static void Main()
    {
        var autoEvt1 = new CustomResetEvent(false, autoReset: true);
        var manualEvt = new CustomResetEvent(false, autoReset: false);
        var autoEvt2 = new CustomResetEvent(false, autoReset: true);

        // 启动线程分别触发事件
        Task.Run(() => { Thread.Sleep(1000); autoEvt1.Set(); Console.WriteLine("自动事件1触发"); });
        Task.Run(() => { Thread.Sleep(2000); manualEvt.Set(); Console.WriteLine("手动事件触发"); });
        Task.Run(() => { Thread.Sleep(3000); autoEvt2.Set(); Console.WriteLine("自动事件2触发"); });

        Console.WriteLine("等待所有事件触发...");
        CustomWaitHandle.WaitAll(autoEvt1, manualEvt, autoEvt2);
        Console.WriteLine("所有事件已触发,继续执行");

        // 测试自动重置事件的复用性
        Task.Run(() => { Thread.Sleep(1000); autoEvt1.Set(); Console.WriteLine("自动事件1再次触发"); });
        Task.Run(() => { Thread.Sleep(2000); autoEvt2.Set(); Console.WriteLine("自动事件2再次触发"); });

        Console.WriteLine("再次等待所有事件触发...");
        CustomWaitHandle.WaitAll(autoEvt1, manualEvt, autoEvt2);
        Console.WriteLine("所有事件再次触发,程序结束");
    }
}

核心逻辑说明

  • 自定义CustomResetEvent通过Monitor.Wait/PulseAll实现信号等待与唤醒,同时支持自动/手动重置:自动重置事件在被等待到后自动恢复未信号状态,手动重置事件保持信号状态直到调用Reset。
  • WaitAll通过共享锁同步状态检查,每次循环确认所有事件是否都触发;若未全部触发则等待,直到任意事件触发后重新检查。所有事件触发后,自动处理自动重置事件的状态重置。
  • 事件触发时会同时唤醒自身等待线程和WaitAll的共享等待线程,确保状态变化能及时被感知。

内容的提问来源于stack exchange,提问作者Ivan Petrov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 14:57:01