一、先给终极结论

Task.WhenAll 和 Task.WhenAny 不是执行器,而是“完成信号的组合器”。

它们:

并发发生在 Task 被“创建/启动”的那一刻,而不是 WhenAll/WhenAny。

二、两者的本质模型(抽象层)

1、 Task.WhenAll —— “全部完成门闩(AND Gate)”

T1 ─┐T2 ─┼─▶ [ All Completed ] ─▶ Completed TaskT3 ─┘

语义:

2、 Task.WhenAny —— “第一个完成门闩(OR Gate)”

T1 ─┐T2 ─┼─▶ [ First Completed ] ─▶ Completed TaskT3 ─┘

语义:

三、底层实现原理(核心机制)

1、WhenAll 的内部机制(简化版)

? 没有线程等待,完全事件驱动

2、WhenAny 的内部机制(简化版)

对每个 Task 注册 continuation

第一个完成的 Task:

后续完成者:

? 只有一个能“赢”,没有计数器

3、为什么 WhenAny 返回Task

WhenAny 返回的是“胜者句柄”,不是结果

四、执行与调度层面的关键差异

维度WhenAllWhenAny
等待策略全部第一个
返回类型Task / TaskTask
是否阻塞线程
continuation 数量NN
内部同步原子计数CAS / TrySet
并发控制

二者都只做“信号组合”,不做“任务调度”

五、异常语义(非常重要)

1、 WhenAll 的异常规则

try{    await Task.WhenAll(tasks);}catch{    var all = tasks        .Where(t => t.IsFaulted)        .SelectMany(t => t.Exception!.InnerExceptions);}

2、 WhenAny 的异常规则

? WhenAny + 异常 = 高风险组合

六、取消语义(常被误解)

1、WhenAll

2、 WhenAny

var winner = await Task.WhenAny(tasks);cts.Cancel(); // 你自己的责任

七、性能特征(底层视角)

1、 WhenAll / WhenAny 本身的成本

? 几乎可以忽略

2、真正的性能瓶颈来自:

八、典型使用范式(工程级)

1、 WhenAll —— 并发聚合(最常用)

var t1 = GetUserAsync();var t2 = GetOrdersAsync();await Task.WhenAll(t1, t2);return new{    User = await t1,    Orders = await t2};

适用场景

2、 WhenAny —— 竞速 / 超时 / 降级

超时模式

var work = DoWorkAsync();var timeout = Task.Delay(2000);if (await Task.WhenAny(work, timeout) == timeout)    throw new TimeoutException();await work;

主备切换

var tasks = new[]{    CallPrimaryAsync(),    CallSecondaryAsync()};var winner = await Task.WhenAny(tasks);return await winner;

⚠️ 记得取消失败者

九、常见错误总结

❌ 把 WhenAll 当“并行器”

❌ 在循环里直接 await(伪并发)

❌ WhenAny 后忽略未完成 Task

❌ WhenAll + Task.Run(Web)

❌ 忽略异常和取消

十、设计层面的黄金准则

并发 = Task 创建时机

组合 = WhenAll / WhenAny

调度 = ThreadPool / Parallel

WhenAll / WhenAny 决定“怎么等”,

而不是“怎么跑”。

十一、一句话终极总结

Task.WhenAll 是一个原子计数器驱动的完成门闩

Task.WhenAny 是一个 CAS 驱动的竞速门闩

它们本身几乎“没有重量”,

但决定了整个 async 架构的形状。

本文转载于:https://www.jb51.net/aspnet/355751k54.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。