c# ContinueWith 和 await 的区别和选择

来源:这里教程网 时间:2026-02-21 17:38:55 作者:

直接说结论:绝大多数情况下该用

await
ContinueWith
只在极少数需要精细控制延续调度、或必须绕过 async/await 语法限制的场景才用——比如写底层异步基础设施、适配旧回调 API、或手动构建状态机。

什么时候会不小心掉进
ContinueWith
的坑

常见错误现象:UI 线程更新崩溃、异常“消失”、任务链意外并行执行、上下文(如

HttpContext
或 UI 同步上下文)丢失。

ContinueWith
默认不捕获当前
SynchronizationContext
,UI 控件赋值(如
label.Text = "done"
)会直接抛
InvalidOperationException
它不会“解包”
AggregateException
,你得手动检查
t.Exception?.InnerExceptions
,否则异常被吞掉
多个
ContinueWith
链式调用时,每个延续都新建一个
Task
,容易造成任务嵌套过深、调试困难
没有
await
的隐式
ConfigureAwait(false)
优化,线程调度更重、开销略高

await
真正省掉的不是代码行数,而是心智负担

它自动处理三件事:上下文恢复、异常扁平化、状态机挂起/恢复。而

ContinueWith
全得你手写。

UI 方法里写
await DoWork()
→ 后续代码自动回到 UI 线程执行;用
ContinueWith
必须显式传
TaskScheduler.FromCurrentSynchronizationContext()
await Task.WhenAll(t1, t2, t3)
抛出单个
Exception
Task.WhenAll(...).ContinueWith(...)
中若任一任务失败,
t.Result
会直接 throw,但你得自己 try/catch 包裹整个延续逻辑
循环中 await 是自然的:
while (cond) { await LoadNext(); }
;用
ContinueWith
模拟等价逻辑要递归构造任务链,极易栈溢出或逻辑错乱

真要用
ContinueWith
的两个典型场景

不是“想用”,而是“不得不”。其他情况基本都是过早优化或历史包袱。

对接无 async 支持的旧库:比如某 SDK 只提供
Task FooAsync()
+
Action<task> onCompleted</task>
回调,这时用
.ContinueWith(t => { ... })
是最直白的胶水层
需要非默认调度策略:例如你想确保延续一定在后台线程跑(避开 UI 上下文),且不希望受
ConfigureAwait(false)
影响,可明确传
TaskScheduler.Default
——
await
本身不提供这种粒度
task.ContinueWith(t =>
{
    // 这里一定在 ThreadPool 线程执行,与调用线程无关
    ProcessInBackground(t.Result);
}, TaskScheduler.Default);

真正复杂的点从来不在语法选择上,而在你是否清楚自己正在放弃什么:用

ContinueWith
就等于主动接管调度、异常传播和上下文生命周期——这些事
await
默默替你扛了五年以上。除非你盯着 IL 看过编译器生成的状态机,否则别轻易动它。

相关推荐