C#中的 Task.WaitAll 与 Task.WhenAll

在C#中,Task.WaitAllTask.WhenAll 都是用于等待多个任务完成的方法,但它们在工作方式和使用场景上有重要区别。

1. 基本区别

Task.WaitAll

  • 同步阻塞:阻塞当前线程直到所有任务完成
  • 返回值:void
  • 异常处理:抛出 AggregateException

Task.WhenAll

  • 异步非阻塞:返回一个Task,不会阻塞当前线程
  • 返回值TaskTask<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,因为它更符合异步编程模式,能避免死锁并提高应用响应性。

posted @ 2025-11-05 11:13  青云Zeo  阅读(6)  评论(0)    收藏  举报