C#中如何将事件回调转换为可等待的async异步任务
最优实现方案
你需要的效果可以通过.NET 内置的TaskCompletionSource<TResult>实现,这是将回调/事件驱动逻辑封装为可等待 Task(TAP 模式)的标准方案,不需要引入额外依赖。
核心封装代码
public static Task<TEvent> WaitForEventAsync<TEvent>(object subscriber, Action triggerAction) { // 配置 RunContinuationsAsynchronously 避免阻塞事件触发线程 var tcs = new TaskCompletionSource<TEvent>(TaskCreationOptions.RunContinuationsAsynchronously); // 定义临时事件处理器 void EventHandler(TEvent eventArgs) { // 收到事件后第一时间取消订阅,避免内存泄漏 EventSystem.Unsubscribe<TEvent>(subscriber, EventHandler); // 完成 Task,传递事件参数 tcs.TrySetResult(eventArgs); } // 先订阅事件再执行触发逻辑,避免事件提前触发丢失 EventSystem.Subscribe<TEvent>(subscriber, EventHandler); try { triggerAction(); } catch (Exception ex) { // 触发操作报错时同步取消订阅,把异常透传到 Task 中 EventSystem.Unsubscribe<TEvent>(subscriber, EventHandler); tcs.TrySetException(ex); } return tcs.Task; }
调用方式
和你期望的写法完全一致:
EventFoo e = await WaitForEventAsync<EventFoo>(this, DoSomethingThatWillTriggerAnEvent);
如果封装为扩展方法,调用会更简洁:
// 扩展方法定义 public static Task<TEvent> WaitForEventAsync<TEvent>(this object subscriber, Action triggerAction) { // 内部逻辑和上面的实现完全一致 } // 调用示例 EventFoo e = await this.WaitForEventAsync<EventFoo>(DoSomethingThatWillTriggerAnEvent);
注意事项
- 必须在事件触发、超时、触发逻辑报错的所有分支中取消事件订阅,避免 EventSystem 持有实例引用导致内存泄漏
- 如果需要超时控制,可以结合
Task.WhenAny实现,示例:
public static async Task<TEvent> WaitForEventAsync<TEvent>(object subscriber, Action triggerAction, TimeSpan timeout) { var tcs = new TaskCompletionSource<TEvent>(TaskCreationOptions.RunContinuationsAsynchronously); void EventHandler(TEvent eventArgs) { EventSystem.Unsubscribe<TEvent>(subscriber, EventHandler); tcs.TrySetResult(eventArgs); } EventSystem.Subscribe<TEvent>(subscriber, EventHandler); try { triggerAction(); var finishedTask = await Task.WhenAny(tcs.Task, Task.Delay(timeout)); if (finishedTask != tcs.Task) { EventSystem.Unsubscribe<TEvent>(subscriber, EventHandler); throw new TimeoutException("等待事件触发超时"); } return await tcs.Task; } catch (Exception ex) { EventSystem.Unsubscribe<TEvent>(subscriber, EventHandler); tcs.TrySetException(ex); throw; } }
- 如果需要接收多次触发的事件,可以将返回值改为
IAsyncEnumerable<TEvent>实现事件流消费。
内容的提问来源于stack exchange,提问作者Kaylen Wheeler
相关产品推荐
相关产品推荐

