Task.WhenAll 抛出的 AggregateException 怎么捕获
直接
catch (AggregateException ex)是最常见也最危险的做法——它会吞掉所有子异常,且无法区分哪些任务失败、哪些成功。真正该做的是解包并分类处理。 必须调用
ex.Flatten(),否则嵌套层级可能很深(比如
Task.Run(() => Task.Run(...))套多层) 解包后遍历
InnerExceptions,逐个检查类型和消息,而不是只看
ex.InnerException如果其中混有非
OperationCanceledException的异常(如
NullReferenceException),说明不是取消导致的失败,需单独响应
如何区分取消异常和真实错误
Task.WhenAll中任一任务被取消,整个聚合异常里大概率包含
OperationCanceledException,但它不总是代表“用户主动取消”——也可能是超时或
CancellationTokenSource.Cancel()被调用。关键看来源。 用
ex.InnerExceptions.OfType<operationcanceledexception>().Any(e => e.CancellationToken == yourToken)</operationcanceledexception>确认是否由你的 token 触发 若存在其他异常(如
HttpRequestException、
SqlException),应优先记录并告警,不能和取消混为一谈 不要依赖
ex.Handle(_ => true)无差别吞掉所有异常,这会让调试变得极其困难
实际捕获与重抛建议写法
下面这段代码展示了安全解包、分类、并选择性重抛的典型模式:
try
{
await Task.WhenAll(tasks);
}
catch (AggregateException ae)
{
var flattened = ae.Flatten();
var cancellationErrors = flattened.InnerExceptions.OfType<OperationCanceledException>()
.Where(e => e.CancellationToken == cancellationToken).ToList();
var otherErrors = flattened.InnerExceptions.Except(cancellationErrors).ToList();
if (otherErrors.Count > 0)
{
// 至少有一个非取消错误:构造新 AggregateException 并重抛
throw new AggregateException("One or more errors occurred during parallel execution.", otherErrors);
}
// 全是预期中的取消:按业务逻辑静默处理或返回默认值
return default(TResult);
}
为什么不用 await Task.WhenAll(...).ConfigureAwait(false)
ConfigureAwait(false)和
AggregateException处理无关——它只影响 await 后的上下文捕获,不改变异常结构或类型。很多人加它是为了避免 UI 线程死锁,但误以为它能“简化异常”。事实是:
Task.WhenAll本身不捕获异常,只是把所有子任务的异常打包进
AggregateException无论是否配置
ConfigureAwait,只要任务出错,await 表达式仍会抛出
AggregateException真正影响异常行为的是你有没有在
await后加
try/catch,以及是否调用
Flatten()复杂点在于:多个任务失败时,
AggregateException可能同时含取消、网络超时、空引用三类异常,而业务逻辑往往只关心其中一种。别图省事只 catch 顶层类型,拆开看才是常态。
