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

如何单独启停并行同步任务?解决ManualResetEvent阻塞问题

问题描述

我有两个持续重复的并行同步Task,分别以2秒和3秒的周期执行任务,需要实现它们的同步——每6秒同时执行任务。我使用私有只读ManualResetEvent _syncEvent实现了该同步逻辑。

但问题在于:我希望能够随时停止任意一个任务,但当前实现中停止一个任务后,另一个任务会因无限等待_syncEvent.WaitOne()而停止运行。如何避免这种情况?

原实现代码

private void ProcessTask1(CancellationToken cancellationToken)
{
    try
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            // 与Task2同步
            _syncEvent.Set();

            if (_periodTask1.Add(TimeSpan.FromMilliseconds(2000)) <= _systemClock.UtcNow)
            {
                //执行任务
                _periodTask1 = _systemClock.UtcNow;
            }
        }
    }
}

private void ProcessTask2(CancellationToken cancellationToken)
{
    try
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            // 等待与Task1同步
            _syncEvent.WaitOne();
            if (_periodTask2.Add(TimeSpan.FromMilliseconds(3000)) <= _systemClock.UtcNow)
            {
                //执行任务
                _periodTask2 = _systemClock.UtcNow;
            }
            // 重置事件以进行下一次同步
            _syncEvent.Reset();
        }
    }
}

2024.03.24 更新(临时解决方案)

我重构了ProcessTask2,添加了_syncEvent的条件判断,虽然能运行,但我认为这不是正确的解决方案。

private void ProcessTask2()
{
        while (!cancellationTokenTask2.IsCancellationRequested)
        {
            // 若Task1未取消则等待同步,否则直接设置事件
            if (cancellationTokenTask1.IsCancellationRequested)
                _syncEvent.Set();
            else 
                _syncEvent.Wait();
                    
            if (_periodTask2.Add(TimeSpan.FromMilliseconds(3000)) <= _systemClock.UtcNow)
            {
                //执行任务
                _periodTask2 = _systemClock.UtcNow;
            }
            // 重置事件以进行下一次同步
            _syncEvent.Reset();
        }
}

解决方案

你的核心问题是ManualResetEvent.WaitOne()的无限等待未结合取消信号,且任务间同步逻辑耦合过强。以下是两种更可靠的实现方式:

方式一:给WaitOne添加超时+绑定取消信号

在等待同步事件时,设置一个小于任务周期的超时时间,每次等待后检查取消信号,同时处理另一个任务已取消的情况:

private void ProcessTask1(CancellationToken cancellationToken)
{
    try
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            // 触发同步信号
            _syncEvent.Set();

            // 按周期执行任务
            if (_periodTask1.Add(TimeSpan.FromSeconds(2)) <= _systemClock.UtcNow)
            {
                // 执行任务逻辑
                _periodTask1 = _systemClock.UtcNow;
            }

            // 短暂等待避免空转,同时响应取消
            Task.Delay(100, cancellationToken).Wait(cancellationToken);
        }
    }
    catch (OperationCanceledException)
    {
        // 任务被取消,正常退出
    }
}

private void ProcessTask2(CancellationToken cancellationToken)
{
    try
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            bool eventSignaled = false;
            // 绑定取消信号:任务取消时主动触发同步事件,避免无限等待
            using (var registration = cancellationToken.Register(() => _syncEvent.Set()))
            {
                // 最多等待1秒(小于3秒周期),同时监听信号
                eventSignaled = _syncEvent.WaitOne(TimeSpan.FromSeconds(1));
            }

            // 检查是否需要执行任务
            if (_periodTask2.Add(TimeSpan.FromSeconds(3)) <= _systemClock.UtcNow)
            {
                // 执行任务逻辑
                _periodTask2 = _systemClock.UtcNow;
            }

            // 仅当信号由Task1触发时才重置,避免Task2自身触发的信号被错误重置
            if (eventSignaled && !cancellationTokenTask1.IsCancellationRequested)
            {
                _syncEvent.Reset();
            }
        }
    }
    catch (OperationCanceledException)
    {
        // 任务被取消,正常退出
    }
}

方式二:使用Task-based同步(推荐)

放弃ManualResetEvent,改用TaskCompletionSource结合Task.Delay实现同步,天然支持CancellationToken,代码更简洁且低耦合:

private TaskCompletionSource<bool> _syncTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly object _syncLock = new object();

private async Task ProcessTask1(CancellationToken cancellationToken)
{
    try
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            // 触发同步通知
            lock (_syncLock)
            {
                if (!_syncTcs.Task.IsCompleted)
                {
                    _syncTcs.SetResult(true);
                }
            }

            // 到点执行任务
            if (_periodTask1.Add(TimeSpan.FromSeconds(2)) <= _systemClock.UtcNow)
            {
                // 执行任务逻辑
                _periodTask1 = _systemClock.UtcNow;
            }

            // 等待下一个周期,同时响应取消
            await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
        }
    }
    catch (OperationCanceledException)
    {
        // 取消时完成同步信号,避免Task2等待
        lock (_syncLock)
        {
            if (!_syncTcs.Task.IsCompleted)
            {
                _syncTcs.SetResult(true);
            }
        }
    }
}

private async Task ProcessTask2(CancellationToken cancellationToken)
{
    try
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
            {
                var syncTask = _syncTcs.Task;
                // 等待同步信号或任务周期超时,先到为准
                var completedTask = await Task.WhenAny(syncTask, Task.Delay(TimeSpan.FromSeconds(3), linkedCts.Token));

                if (completedTask == syncTask)
                {
                    // 同步信号触发,重置TCS准备下一次
                    lock (_syncLock)
                    {
                        _syncTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
                    }
                }
            }

            // 到点执行任务
            if (_periodTask2.Add(TimeSpan.FromSeconds(3)) <= _systemClock.UtcNow)
            {
                // 执行任务逻辑
                _periodTask2 = _systemClock.UtcNow;
            }
        }
    }
    catch (OperationCanceledException)
    {
        // 任务被取消,正常退出
    }
}

关键改进点

  • 所有等待操作都绑定CancellationToken,任务取消时能立即响应,不会无限阻塞
  • 降低任务耦合:一个任务取消时,主动触发同步信号或完成等待源,让另一个任务能继续运行
  • 使用异步等待替代传统线程阻塞,更符合.NET现代编程模式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:12:48