如何在WinUI的DispatcherQueue中执行异步代码且不使用async void?
不使用async void实现DispatcherQueue异步主线程执行的最佳方式
核心思路
通过TaskCompletionSource桥接DispatcherQueue.TryEnqueue的同步委托要求与异步逻辑,将异步逻辑封装在async Task方法中,避免直接使用async void委托,同时保证异常能正确传递给调用者,遵循微软的异步线程规则。
改进后的实现代码
public static async Task Execute(this DispatcherQueue dispatcher, Func<Task> function) { // 已在主线程,直接执行异步方法 if (dispatcher.HasThreadAccess) { await function(); return; } // 使用RunContinuationsAsynchronously避免续延在Dispatcher线程同步执行引发死锁 var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); // 传入同步Action,内部调用异步处理方法,避免async void var enqueued = dispatcher.TryEnqueue(() => { // 启动异步任务,无需等待(由TCS跟踪完成状态) _ = CompleteTask(function, taskCompletionSource); }); if (!enqueued) { // 入队失败时主动设置异常 taskCompletionSource.SetException(new InvalidOperationException("无法将任务排入DispatcherQueue")); } // 等待异步任务完成 await taskCompletionSource.Task; } // 封装异步逻辑的async Task方法,避免async void private static async Task CompleteTask(Func<Task> function, TaskCompletionSource<bool> tcs) { try { await function(); // 尝试设置完成状态,防止TCS已被处置 tcs.TrySetResult(true); } catch (Exception ex) { // 传递异常给调用者 tcs.TrySetException(ex); } }
方案优势
- 避免async void风险:传入
TryEnqueue的是同步Action,内部调用的是async Task方法,不会产生async void委托,彻底规避了其导致的进程崩溃、无法追踪异常等问题 - 异常可控传递:所有异常都会被捕获并通过
TaskCompletionSource传递给调用者,调用者可通过await或Task.Exception处理异常,支持遥测系统上报 - 支持任务等待:调用者可
await返回的Task,VS包在Package.Close时能正常等待该异步工作完成 - 死锁防护:
TaskCreationOptions.RunContinuationsAsynchronously确保TCS的续延不会在Dispatcher线程同步执行,避免潜在死锁
补充说明(翻译自原提问)
我了解DispatcherQueue.TryEnqueue的重载方法接受的是不返回Task的Action或Func参数。我的问题在于如何弥合同步到异步的间隙,同时遵循微软的线程规则第4条:永远不要定义async void方法,应改为返回Task。该规则指出:
- async void方法抛出的异常会直接导致进程崩溃
- 调用者无法选择await结果
- 调用者无法将异常上报至遥测系统
- 若以此方式启动异步工作,VS包无法在Package.Close时等待工作完成
- 注意:当异步委托或
async () =>被传入接受Action委托的方法时,会成为async void方法,仅应将异步委托传入接受Func或Func<Task >参数的方法
内容的提问来源于stack exchange,提问作者Batesias
相关产品推荐
相关产品推荐

