绝大多数情况下该用 await,ContinueWith 仅适用于需精细控制调度、适配旧回调 API 或构建底层异步设施等极少数场景。

直接说结论:绝大多数情况下该用 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 全得你手写。
大众投资指南是基于Asp.Net(2.0)+C#+Access(sql2000)的企业黄页类程序,是基于web2.0 模式的网站。 贴吧和黄页都有采集功能 主程序包括分类信息和商家黄页两大模块。分类信息支持二级分类,商家黄页支持二级地区分类及二级行业分类。程序采用了伪静态(url重写)技术,可选生成纯静态首页。 一、分类信息仿百度贴吧编写,可以分别对游客及会员设置不同的审核条件。会员发布信息
- 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回调,这时用onCompleted .ContinueWith(t => { ... })是最直白的胶水层 -
需要非默认调度策略:例如你想确保延续一定在后台线程跑(避开 UI 上下文),且不希望受
ConfigureAwait(false)影响,可明确传TaskScheduler.Default——await本身不提供这种粒度
task.ContinueWith(t =>
{
// 这里一定在 ThreadPool 线程执行,与调用线程无关
ProcessInBackground(t.Result);
}, TaskScheduler.Default);
真正复杂的点从来不在语法选择上,而在你是否清楚自己正在放弃什么:用 ContinueWith 就等于主动接管调度、异常传播和上下文生命周期——这些事 await 默默替你扛了五年以上。除非你盯着 IL 看过编译器生成的状态机,否则别轻易动它。









