You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:Task.WhenAll聚合异常未捕获TaskCanceledException的问题

问题:Task.WhenAll中TaskCanceledException未被包含进AggregateException的处理方案

当捕获Task.WhenAll抛出的AggregateException时,若存在其他故障任务,TaskCanceledException不会被包含其中。运行以下代码后得到对应输出:

TaskException
Caught in AggregateException
System.ArgumentOutOfRangeException

TaskCanceledException
Caught in TaskCanceledException

TaskException-TaskCanceledException
Caught in AggregateException
System.ArgumentOutOfRangeException

在最后一项测试中,TaskCanceledException既未被单独抛出,也未出现在AggregateException内。请问是否有办法让TaskCanceledException被包含在AggregateException中,或者是否必须在其他任务出错时检查每个任务的异常?

对应的测试代码如下:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;

public static class Program
{
    public static void Main()
    {
        var task1 = Task.FromException(new ArgumentOutOfRangeException());
        var task2 = Task.FromCanceled(new CancellationToken(true));
        Test("TaskException", new[] { task1 });
        Test("TaskCanceledException", new[] { task2 });
        Test("TaskException-TaskCanceledException", new Task[] { task1, task2 });

        static async void Test(string title, Task[] tasks)
        {
            Console.WriteLine();
            Console.WriteLine(title);
            var task = Task.WhenAll(tasks);
            try { await task; }            
            catch (TaskCanceledException)
            {
                Console.WriteLine($"Caught in TaskCanceledException");
            }
            catch
            {
                Console.WriteLine($"Caught in AggregateException");
                if (task.Exception is not null)
                {
                    var t = task.Exception.Flatten();
                    foreach (var x in t.InnerExceptions)
                    {
                        Console.WriteLine(x.GetType());
                    }                   
                }
            }
        }        
    }
}

解决方案

首先明确:Task.WhenAll的默认行为是,只要有一个任务进入故障状态(Faulted),最终的聚合任务就会以Faulted状态结束,此时只会收集所有故障任务的异常,已取消的任务不会被纳入AggregateException。只有当所有任务都被取消,或者没有故障任务但有取消任务时,才会抛出TaskCanceledException。

方法一:将取消任务包装为故障任务

把每个可能被取消的任务转换成抛出TaskCanceledException的故障任务,这样Task.WhenAll会将其异常纳入聚合异常:

static Task WrapCancelAsFault(Task task)
{
    return task.ContinueWith(t =>
    {
        if (t.IsCanceled)
            throw new TaskCanceledException(t);
        else if (t.IsFaulted)
            throw t.Exception!.Flatten();
        // 任务成功则直接完成
    }, TaskContinuationOptions.ExecuteSynchronously);
}

在Test方法中使用包装后的任务:

var wrappedTasks = tasks.Select(WrapCancelAsFault).ToArray();
var task = Task.WhenAll(wrappedTasks);

修改后测试第三个场景,AggregateException里会同时包含ArgumentOutOfRangeException和TaskCanceledException。

方法二:手动检查所有任务状态

如果不想修改原始任务的状态,可在Task.WhenAll完成后遍历每个任务单独检查状态:

try { await task; }
catch
{
    Console.WriteLine($"Caught in AggregateException");
    // 先处理故障任务的异常
    if (task.Exception is not null)
    {
        var t = task.Exception.Flatten();
        foreach (var x in t.InnerExceptions)
        {
            Console.WriteLine(x.GetType());
        }
    }
    // 再检查是否有被取消的任务
    foreach (var t in tasks)
    {
        if (t.IsCanceled)
        {
            Console.WriteLine(typeof(TaskCanceledException));
        }
    }
}

这种方式无需修改原始任务状态,适合需要保留任务原始状态的场景。


内容的提问来源于stack exchange,提问作者clonesegg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 20:59:52