如何确保UI与非UI代码中ContinueWith和await行为等价
实现与await等价的Task.ContinueWith逻辑
要完全复刻await的行为,需要解决两个核心问题:
- 安全获取当前上下文对应的TaskScheduler,避免无有效同步上下文时抛出异常
- 正确传播原任务的异常和取消状态,和
await的异常处理逻辑对齐
问题分析
你原来的代码存在两个关键缺陷:
- 直接调用
TaskScheduler.FromCurrentSynchronizationContext()时,若当前无有效同步上下文(如Mono WASM无UI场景),会抛出InvalidOperationException - 未处理原任务的异常/取消状态,导致原任务出错时异常被静默吞掉,和
await的异常传播行为不一致
解决方案
1. 安全获取当前调度器
先尝试从当前同步上下文创建调度器,若失败则回退到当前TaskScheduler(无特殊调度器时使用线程池默认调度器):
using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; TaskScheduler GetSafeCurrentScheduler() { try { var syncContext = SynchronizationContext.Current; if (syncContext != null) { return TaskScheduler.FromCurrentSynchronizationContext(); } } catch (InvalidOperationException) { // 捕获上下文无法转换为TaskScheduler的异常(如部分非UI同步上下文) } // 无有效同步上下文时,使用当前TaskScheduler,默认回退到线程池调度器 return TaskScheduler.Current; }
2. 带异常处理的ContinueWith逻辑
使用上述安全获取的调度器,并在延续委托中处理原任务的异常和取消状态,确保和await的行为完全一致:
DoSomethingAsync().ContinueWith( t => { // 复刻await的异常传播逻辑 if (t.IsFaulted) { // 保留原始异常的调用栈 ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw(); } else if (t.IsCanceled) { throw new TaskCanceledException(t); } // 执行延续操作 DoSomethingElse(); }, GetSafeCurrentScheduler());
行为说明
- 当存在有效UI同步上下文(如WPF的DispatcherSynchronizationContext)时,延续操作
DoSomethingElse()会在原UI线程执行 - 当无有效同步上下文时,延续操作会在当前TaskScheduler对应的线程执行(无特殊调度器时默认使用线程池线程)
- 原任务的异常和取消状态会被正确传播,和
await的行为完全一致
内容的提问来源于stack exchange,提问作者Emperor Eto
相关产品推荐
相关产品推荐

