请问在 C# 编程中,Task.WaitAll 和 Task.WaitAny 方法各自的作用是什么?它们通常应用在哪些场景下?
考察说明
考察对 C# 中 Task 等待机制的理解,以及 WaitAll 与 WaitAny 的区别和应用场景。
回答思路
- 【回答框架 1】Task.WaitAll 用于等待提供的所有 Task 对象完成执行,它是一个同步阻塞方法,会阻塞当前线程直到所有任务完成。如果任一任务发生异常,WaitAll 会抛出一个 AggregateException,其中包含所有未处理异常的信息。
- 【回答框架 2】Task.WaitAny 用于等待提供的多个 Task 对象中的任何一个完成,它同样是一个同步阻塞方法,但只等待第一个完成的任务,并返回该任务在数组中的索引。如果其中某个任务发生异常,WaitAny 通常不会等待其他任务,但异常会在后续对该任务的观察中暴露。
- 【回答框架 3】WaitAll 适用于需要所有任务都完成才能继续执行的场景,例如并行计算多个不相关的结果,最后汇总时使用;WaitAny 适用于只要有一个任务完成就可以继续的场景,例如竞态条件或超时控制,当第一个任务完成时即可获取结果或取消剩余任务。
- 【回答框架 4】需要注意的是,WaitAll 和 WaitAny 都是同步阻塞的,如果在 UI 线程上使用会导致界面卡顿,现代 C# 开发中更推荐使用 await Task.WhenAll 和 await Task.WhenAny,因为它们是异步的,不会阻塞线程。此外,WaitAll 和 WaitAny 只能接受 Task 数组或可枚举集合,并且不支持传入取消令牌或超时参数,简单场景下可直接使用。
- 【关键点 1】Task.WaitAll 会阻塞当前线程,直到所有任务完成,异常会聚合为 AggregateException。
- 【关键点 2】Task.WaitAny 返回第一个完成的任务索引,适合竞争场景。
- 【关键点 3】在 UI 线程上应使用 await Task.WhenAll/WhenAny 避免阻塞。
- 【易错点 1】WaitAll 只抛出第一个异常,其余异常需要从 AggregateException 中遍历获取,可能遗漏细节。
- 【易错点 2】WaitAll 在任务非常耗时的情况下可能导致线程池饥饿,因为阻塞线程占用了线程资源。
- 【易错点 3】WaitAny 只等待第一个完成的任务,如果该任务后续抛出异常,需要在后续代码中显式观察该任务的状态,否则可能导致未观察到的异常。