在C#中,Task.WaitAll 和 Task.WhenAll 都是用于等待多个任务完成的方法,但它们在工作方式和使用场景上有重要区别。
1. 基本区别
Task.WaitAll
- 同步阻塞:阻塞当前线程直到所有任务完成
- 返回值:void
- 异常处理:抛出
AggregateException
Task.WhenAll
- 异步非阻塞:返回一个Task,不会阻塞当前线程
- 返回值:
Task或Task<T[]> - 异常处理:返回的Task包含异常信息
2. 代码示例
Task.WaitAll 示例
async Task WaitAllExample()
{Task task1 = Task.Delay(1000);Task task2 = Task.Delay(2000);Task task3 = Task.Delay(1500);// 阻塞当前线程,直到所有任务完成Task.WaitAll(task1, task2, task3);Console.WriteLine("所有任务已完成");
}
Task.WhenAll 示例
async Task WhenAllExample()
{Task task1 = Task.Delay(1000);Task task2 = Task.Delay(2000);Task task3 = Task.Delay(1500);// 异步等待,不阻塞当前线程await Task.WhenAll(task1, task2, task3);Console.WriteLine("所有任务已完成");
}
3. 返回值处理
有返回值的任务
async Task<int> GetValue1() => await Task.Run(() => 1);
async Task<int> GetValue2() => await Task.Run(() => 2);
async Task<int> GetValue3() => await Task.Run(() => 3);// 使用 WhenAll 获取所有结果
async Task ProcessWithResults()
{Task<int> task1 = GetValue1();Task<int> task2 = GetValue2();Task<int> task3 = GetValue3();int[] results = await Task.WhenAll(task1, task2, task3);Console.WriteLine($"结果: {string.Join(", ", results)}"); // 输出: 1, 2, 3
}
4. 异常处理差异
Task.WaitAll 异常处理
void WaitAllExceptionHandling()
{Task task1 = Task.Run(() => throw new Exception("错误1"));Task task2 = Task.Run(() => throw new Exception("错误2"));try{Task.WaitAll(task1, task2);}catch (AggregateException ex){foreach (var innerEx in ex.InnerExceptions){Console.WriteLine($"捕获异常: {innerEx.Message}");}}
}
Task.WhenAll 异常处理
async Task WhenAllExceptionHandling()
{Task task1 = Task.Run(() => throw new Exception("错误1"));Task task2 = Task.Run(() => throw new Exception("错误2"));Task allTasks = Task.WhenAll(task1, task2);try{await allTasks;}catch (Exception ex){// 注意:WhenAll 只抛出第一个异常Console.WriteLine($"捕获异常: {ex.Message}");// 如果要获取所有异常,需要检查每个任务if (allTasks.IsFaulted && allTasks.Exception != null){foreach (var innerEx in allTasks.Exception.InnerExceptions){Console.WriteLine($"所有异常: {innerEx.Message}");}}}
}
5. 如何选择使用哪个
使用 Task.WaitAll 的情况
// 在同步方法中需要等待多个任务
void SynchronousMethod()
{// 在控制台应用或后台服务中var tasks = new[]{ProcessDataAsync(),SaveToDatabaseAsync(),SendNotificationAsync()};Task.WaitAll(tasks); // 阻塞直到完成Console.WriteLine("所有处理完成");
}
使用 Task.WhenAll 的情况
// 在异步方法中 - 推荐使用
async Task AsynchronousMethod()
{// 在Web API、UI应用或任何异步上下文中var tasks = new[]{ProcessDataAsync(),SaveToDatabaseAsync(),SendNotificationAsync()};await Task.WhenAll(tasks); // 不阻塞线程Console.WriteLine("所有处理完成");
}
6. 最佳实践建议
推荐使用 Task.WhenAll
public class DataProcessor
{// 好的做法 - 使用 WhenAllpublic async Task ProcessAllDataAsync(){var tasks = new List<Task>();for (int i = 0; i < 10; i++){tasks.Add(ProcessItemAsync(i));}await Task.WhenAll(tasks);}// 避免的做法 - 在异步代码中使用 WaitAllpublic async Task ProcessAllDataBadAsync(){var tasks = new List<Task>();for (int i = 0; i < 10; i++){tasks.Add(ProcessItemAsync(i));}// 可能造成死锁!Task.WaitAll(tasks.ToArray());}private async Task ProcessItemAsync(int item){await Task.Delay(100);// 处理逻辑}
}
7. 性能考虑
async Task PerformanceComparison()
{var stopwatch = Stopwatch.StartNew();// 使用 WhenAll - 更高效Task[] tasks = new Task[100];for (int i = 0; i < 100; i++){tasks[i] = Task.Delay(100);}await Task.WhenAll(tasks);Console.WriteLine($"WhenAll 耗时: {stopwatch.ElapsedMilliseconds}ms");// 重新计时stopwatch.Restart();// 使用 WaitAll - 可能造成线程阻塞tasks = new Task[100];for (int i = 0; i < 100; i++){tasks[i] = Task.Delay(100);}Task.WaitAll(tasks);Console.WriteLine($"WaitAll 耗时: {stopwatch.ElapsedMilliseconds}ms");
}
总结
| 特性 | Task.WaitAll | Task.WhenAll |
|---|---|---|
| 阻塞性 | 同步阻塞 | 异步非阻塞 |
| 返回值 | void | Task 或 Task<T[]> |
| 异常处理 | AggregateException | 通过返回的Task处理 |
| 使用场景 | 同步方法中 | 异步方法中 |
| 死锁风险 | 较高 | 较低 |
| 性能 | 可能阻塞线程 | 更高效 |
一般建议:在大多数现代C#开发中,特别是在ASP.NET Core、Web API、UI应用等场景下,优先使用 Task.WhenAll,因为它更符合异步编程模式,能避免死锁并提高应用响应性。