如何单独启停并行同步任务?解决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
相关产品推荐
相关产品推荐

