TaskContinuationOptions用来精确控制「延续任务(continuation)」何时执行、怎么调度、和前置任务如何关联。它不是可有可无的配置项,而是决定你
ContinueWith行为是否符合预期的关键开关。
什么时候必须指定 TaskContinuationOptions
?
当你需要延续任务只在特定状态才运行(比如只处理异常),或要求它同步执行、绑定父任务生命周期、避免线程池争抢时,就必须显式传入。默认不传等价于
TaskContinuationOptions.None—— 它会异步执行、不关心前置任务成败、也不参与父子关系管理。 前置任务失败了你还想记录日志?用
TaskContinuationOptions.NotOnRanToCompletion | TaskContinuationOptions.NotOnCanceled想让延续逻辑一定在同一线程跑(比如 UI 更新)?加
TaskContinuationOptions.ExecuteSynchronously(但注意:仅当前置任务已完成才生效,否则仍异步) 写了一个嵌套子任务链,希望父任务
Wait()时等所有子任务结束?必须用
TaskContinuationOptions.AttachedToParent
AttachedToParent
和 DenyChildAttach
的真实作用
这两个选项管的是「任务树结构」,不是线程绑定。很多开发者误以为
AttachedToParent能让子任务和父任务跑在同一个线程上 —— 实际完全无关。它只影响
Task.Status和
Wait()行为: 父任务设了
AttachedToParent,它的
Status不会变成
RanToCompletion直到所有附加子任务也完成 如果父任务用了
TaskCreationOptions.DenyChildAttach,那么子任务里再写
AttachedToParent也会被无视,直接退化为独立任务 错误示范:
Task.Run(...).ContinueWith(..., AttachedToParent)—— 这里
ContinueWith产生的任务不是「子任务」,
AttachedToParent无效(只有
new Task(..., AttachedToParent)或
Task.Factory.StartNew(..., AttachedToParent)才触发父子关系)
常见踩坑点:位运算、条件组合与多任务延续
TaskContinuationOptions是带
[Flags]特性的枚举,支持按位或(
|)组合多个行为,但不是所有组合都合理:
ExecuteSynchronously | NotOnFaulted合法;但
ExecuteSynchronously | OnlyOnFaulted可能导致延续根本没机会执行(因为异常发生时前置任务已非 Running 状态,同步执行条件不满足)
OnlyOnRanToCompletion等「OnlyOnXxx」系列不能用于
ContinueWhenAll或
ContinueWhenAny—— 多任务延续不支持这些条件,会抛
ArgumentException别把
PreferFairness当成「保证顺序」:它只是给
TaskScheduler的提示,实际调度仍取决于线程池负载和窃取策略
Task taskA = Task.Run(() => { throw new InvalidOperationException(); });
Task continuation = taskA.ContinueWith(t =>
{
Console.WriteLine("我不会执行,因为用了 OnlyOnRanToCompletion");
}, TaskContinuationOptions.OnlyOnRanToCompletion); // 此延续被跳过
最常被忽略的一点:
ContinueWith默认是「火后不管」型延续 —— 前置任务一完成,延续就进队列,哪怕你没 await、没 Wait、也没保存引用,它照样可能执行(也可能被 GC 中断,取决于是否捕获异常)。要真正稳控流程,得结合
NotOnXxx条件 + 显式等待 + 异常处理,而不是依赖默认行为。
