C#中的 Task.WaitAll 与 Task.WhenAll
在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
{
// 好的做法 - 使用 WhenAll
public async Task ProcessAllDataAsync()
{
var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
tasks.Add(ProcessItemAsync(i));
}
await Task.WhenAll(tasks);
}
// 避免的做法 - 在异步代码中使用 WaitAll
public 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,因为它更符合异步编程模式,能避免死锁并提高应用响应性。

浙公网安备 33010602011771号