Task.WhenAll等待所有任务完成,适合批量操作;Task.WhenAny响应首个完成的任务,适用于超时或降级场景。两者不启动任务,需先并行创建任务再传入,注意异常处理与并发控制。

Task.WhenAll 和 Task.WhenAny 是 C# 中协调多个异步任务的核心工具,它们不执行任务,而是“观察”任务的完成状态,帮你决定下一步怎么走。
Task.WhenAll:等全部跑完再继续
当你需要所有异步操作都成功完成(比如批量获取用户数据、并行上传多个文件),就用 WhenAll。它返回一个新任务,这个任务在所有输入任务都完成后才完成;如果任一任务出错或被取消,它会立刻失败,并抛出包含所有异常的 AggregateException。
- 传入
IEnumerable<task></task>或Task[],返回Task;传入Task<t>[]</t>,返回Task<t></t> - 结果数组顺序与输入顺序严格一致,哪怕实际完成顺序不同
- 注意异常处理:建议用
try/catch捕获AggregateException,再遍历InnerExceptions分析具体失败项
Task.WhenAny:谁先回来就先处理谁
适合“抢答”场景,比如设置超时、从多个服务中取最快响应、或实现降级逻辑。它返回一个 Task<task></task>,里面包裹最先完成的那个原始任务(无论成功、失败还是取消)。
- 拿到结果后,要 await 那个内部 Task 才能真正读取值或捕获异常
- 其余未完成的任务不会自动取消,需手动调用
CancellationTokenSource.Cancel()或保留引用后续处理 - 常配合
Task.Delay()实现超时控制,例如:await Task.WhenAny(apiCall, Task.Delay(3000)) == apiCall ? ... : "超时"
别混淆:它们不是并发执行器
WhenAll 和 WhenAny 本身不启动任务——你得先调用异步方法(如 GetDataAsync())拿到 Task 对象,再把它们传进去。常见错误是写成 WhenAll(GetDataAsync(), GetDataAsync()),这其实是同步调用两次,不是并行发起。
- 正确做法:先创建任务列表,再统一传入
WhenAll - 示例:
var tasks = urls.select(u => HttpClient.GetAsync(u)); await Task.WhenAll(tasks); - 若想限制并发数(比如最多同时请求 5 个),要用
SemaphoreSlim或Parallel.ForEachAsync(.net 6+)
选哪个?看你的业务意图
问自己一个问题:“我关心的是所有结果,还是第一个结果?”
- 要汇总、校验、批量提交 → 选
WhenAll - 要快速响应、容错降级、避免等待 → 选
WhenAny - 两者可嵌套使用,比如用
WhenAny控制整体超时,内部用WhenAll并行拉取子数据
基本上就这些。用对了,异步代码既清晰又健壮。