咨询:Task.WhenAll聚合异常未捕获TaskCanceledException的问题
当捕获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

