如何实现AutoResetEvent等待并支持调用取消后终止等待?
实现可取消的事件等待:方案分析与优化建议
你的核心需求是在等待外部事件触发的过程中,能够通过CancellationToken主动终止等待,下面先分析你给出的两个方案的问题,再提供更简洁优雅的实现方式。
方案1分析
基于AutoResetEvent+CancellationToken.Register的方案存在三个明显问题:
- 内存泄漏风险:通过
cancellationToken.Register注册的回调未被取消注册,若CancellationToken生命周期长于MyClass,会导致_dataArrivedSignal被回调持有无法被GC回收; - 取消响应滞后:仅在
WaitOne返回后才检查取消信号,若取消在DoSomeWork执行期间触发,无法立即终止循环; - 同步与异步模型混用:用
Task.Factory.StartNew启动同步阻塞的循环,未发挥异步编程的资源利用优势。
方案2分析
基于TaskCompletionSource的方案同样存在缺陷:
- 线程安全隐患:
_waiter字段与_awaiters队列的同步逻辑不严谨,多线程场景下可能出现等待的TaskCompletionSource被重复设置或遗漏的情况; - 阻塞调用浪费资源:使用
waiter.Task.Wait(cancellationToken)属于同步阻塞,未利用异步模型的非阻塞特性; - 冗余设计:
_awaiters队列与_waiter字段职责重叠,逻辑可进一步简化。
更优雅的实现方式
推荐使用**异步等待+TaskCompletionSource结合CancellationToken**的方式,既避免线程阻塞,又能高效响应取消信号,同时解决资源泄漏问题。
实现思路
- 将事件触发转换为可等待的
Task,用TaskCompletionSource作为事件与异步代码的桥梁; - 利用
CancellationToken.Register在取消时完成TaskCompletionSource,同时通过using管理回调注册,确保资源释放; - 使用
await替代同步阻塞,充分利用线程池资源; - 复用
TaskCompletionSource(或在每次等待前重置),避免频繁创建对象。
代码示例(通用.NET版本)
class MyClass { private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); private readonly object _lockObj = new object(); public async Task RunAsync(CancellationToken cancellationToken) { while (/* 你的循环条件 */ true) { DoSomeWork(); // 注册取消回调,并用using确保回调被释放 using var registration = cancellationToken.Register(() => { lock (_lockObj) { _tcs.TrySetCanceled(); } }); try { await _tcs.Task; } finally { // 重置TCS用于下一次等待 lock (_lockObj) { _tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); } } } } private void OnDataArrived(EventArgs args) { lock (_lockObj) { _tcs.TrySetResult(true); } } private void DoSomeWork() { // 你的业务逻辑实现 } }
.NET 5+简化版(利用官方异步同步原语)
如果使用.NET 5及以上版本,可直接使用AsyncManualResetEvent(需引用System.Threading.Tasks.Dataflow包),代码更简洁且线程安全:
class MyClass { private readonly AsyncManualResetEvent _asyncResetEvent = new AsyncManualResetEvent(false); public async Task RunAsync(CancellationToken cancellationToken) { while (/* 你的循环条件 */ true) { DoSomeWork(); // 等待事件触发或取消信号 await _asyncResetEvent.WaitAsync(cancellationToken); // 重置信号,准备下一次等待 _asyncResetEvent.Reset(); } } private void OnDataArrived(EventArgs args) { _asyncResetEvent.Set(); } private void DoSomeWork() { // 你的业务逻辑实现 } }
关键优化点
- 异步非阻塞:用
await替代同步阻塞,避免占用线程池线程; - 资源安全:通过
using管理CancellationTokenRegistration,避免内存泄漏; - 线程安全:用锁或官方原语保护同步操作,避免多线程冲突;
- 即时响应取消:取消信号触发时立即终止等待,无需等待阻塞操作完成。
内容的提问来源于stack exchange,提问作者theateist
相关产品推荐
相关产品推荐

