在 .NET 并发编程里,Task 几乎是每个项目都离不开的工具。当我们同时启动好几个任务时,主线程该在什么时机继续往下走,就成了一个绕不开的问题。这时候,Task.WaitAll 和 Task.WaitAny 就派上用场了。这两个方法名字长得像,但用途和场景完全不同,刚接触时很容易搞混,甚至用错。
这篇文章不打算罗列枯燥的定义,而是从实际开发场景出发,帮你把这两个方法的区别和使用方式理清楚。
一、先搞明白:我们到底在“等什么”?
当你同时启动多个 Task,本质上是在并发做几件事,比如:
同时请求几个不同的接口; 并行处理一批数据; 同时访问多个外部资源。
这时,主线程面临一个选择:到底等到什么时候,才继续往下执行?
有两种常见的思路:
所有任务都完成,再继续; 只要有一个完成,就可以继续。
这两种思路,正好对应 Task.WaitAll 和 Task.WaitAny。
二、Task.WaitAll:等所有人都到齐
使用方式
Task t1 = Task.Run(() => DoWork(1));
Task t2 = Task.Run(() => DoWork(2));
Task t3 = Task.Run(() => DoWork(3));
Task.WaitAll(t1, t2, t3);
Console.WriteLine("所有任务执行完成");
它在做什么?
Task.WaitAll 会阻塞当前线程,直到所有传入的任务都执行完成(无论是成功还是失败,都算结束)。可以把它想象成组织一场聚餐,必须所有人都到齐了,才能开饭。
适用场景
当你“必须拿到全部结果”时,用它最合适:
批量数据处理,要求所有批次都处理完; 多个文件都处理完毕,再统一汇总; 依赖多个接口的数据,等所有接口都返回后再拼装。
一个常见坑
如果其中某个任务抛了异常,WaitAll 会直接抛出 AggregateException(聚合异常),里面包裹着所有任务的异常信息:
try
{
Task.WaitAll(tasks);
}
catch (AggregateException ex)
{
// 处理聚合异常
}
再提醒一句
⚠️ WaitAll 是同步阻塞的,会卡住当前线程。在 UI 程序或 ASP.NET Core 里直接使用,可能导致界面卡死或线程池耗尽。
更现代的做法是用它的异步版本:
await Task.WhenAll(tasks);
三、Task.WaitAny:谁先完成听谁的
使用方式
Task t1 = Task.Run(() => DoWork(1));
Task t2 = Task.Run(() => DoWork(2));
Task t3 = Task.Run(() => DoWork(3));
int index = Task.WaitAny(t1, t2, t3);
Console.WriteLine($"第 {index} 个任务先完成");
它在做什么?
Task.WaitAny 同样会阻塞线程,但它只关心哪一个任务最先完成。只要有一个任务结束,它就立即返回。可以想象成点外卖,谁先送到就先吃,不等其他人。
返回值很关键
int index = Task.WaitAny(tasks);
返回的是最先完成的任务在数组中的索引,可以通过这个索引拿到对应的任务:
Task first = tasks[index];
适用场景
这种模式特别适合“竞速”场景:
同时请求多个镜像服务,取最快返回的那个; 并发搜索,谁先找到结果就用谁; 配合 CancellationToken实现超时控制。
一个常见例子
比如你有多个数据源,想取最快返回的那个:
var tasks = new[]
{
Task.Run(() => GetDataFromA()),
Task.Run(() => GetDataFromB()),
Task.Run(() => GetDataFromC())
};
int index = Task.WaitAny(tasks);
var result = tasks[index].Result; // 谁快用谁
这种方式能显著提升响应速度。
四、核心区别,一眼看懂
AggregateException
五、和 async/await 的关系
有人会问:现在都推荐用 async/await 了,这两个同步方法还有用吗?
答案是:可以用,但在现代代码中更推荐用它们的异步版本:
await Task.WhenAll(tasks); // 替代 WaitAll
await Task.WhenAny(tasks); // 替代 WaitAny
区别在于:
await版本不阻塞线程;更适合高并发场景(尤其是 Web 应用); 不容易引发死锁。
六、一个简单的判断原则
在实际项目中,用一句话就能判断该用哪个:
👉 要不要等所有结果?
要 👉 用 WaitAll/WhenAll不要 👉 用 WaitAny/WhenAny
再进一步:
👉 你写的是现代 .NET 代码吗?
是 👉 优先用 await WhenAll/await WhenAny否 👉 才考虑用 WaitAll/WaitAny
七、结尾:不止于会用,更要知道为什么用
Task.WaitAll 和 Task.WaitAny 看起来只是两个 API,但背后其实是两种完全不同的并发思维:
一个是“同步汇总”; 一个是“竞争优先”。
写代码不只是让它跑起来,更重要的是让它跑得更合理、更高效。
下次面对多个 Task 时,不妨先问自己一句:
我是要“全部结果”,还是“最快结果”?
答案自然就清晰了。