如何在DotNet中捕获Task.WhenAll并行任务的所有异常?
解决Task.WhenAll并行处理时捕获全部异常的问题
问题根源
Task.WhenAll会将所有失败任务的异常包装进AggregateException,但多数自定义异常中间件默认只会提取并处理该聚合异常中的第一个内部异常,导致其余异常被忽略。
可行解决方案
1. 手动捕获聚合异常并包装传递
在调用Task.WhenAll的业务代码中显式捕获AggregateException,收集所有内部异常后,用自定义异常包装后重新抛出,确保中间件能拿到完整异常集合:
try { // 初始化你的12个数据库查询任务 var exportTasks = GetExportDatabaseTasks(); await Task.WhenAll(exportTasks); } catch (AggregateException aggEx) { // 收集所有异常详情 var allExceptionDetails = aggEx.InnerExceptions .Select(ex => $"[{ex.GetType().Name}] {ex.Message}") .ToList(); // 抛出包含全部异常的自定义异常 throw new MultiExportException("导出任务出现多个异常", allExceptionDetails); }
之后在你的ExceptionHandlerMiddleware中针对MultiExportException做处理,即可获取所有异常信息。
2. 改造异常中间件直接处理AggregateException
修改中间件逻辑,当捕获到AggregateException时,遍历其所有内部异常并整合信息,而非只处理第一个:
public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { var errorMessages = new List<string>(); // 处理聚合异常,提取所有内部异常信息 if (ex is AggregateException aggregateEx) { errorMessages.AddRange( aggregateEx.InnerExceptions .Select(e => $"任务异常: {e.Message}") ); } else { errorMessages.Add($"异常: {ex.Message}"); } // 构建统一错误响应 context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; await context.Response.WriteAsJsonAsync(new { Errors = errorMessages }); } }
并发风险说明
Task.WhenAll本身不存在并发安全问题,它仅负责等待所有任务完成。需要注意的是数据库查询任务本身的线程安全:
- 若使用EF Core,每个DbContext实例不是线程安全的,必须保证每个查询任务使用独立的DbContext(比如通过依赖注入的
IServiceScope在任务内创建); - 数据库层面的并发冲突(如行锁、乐观锁),需根据业务场景添加重试逻辑或调整事务隔离级别。
内容的提问来源于stack exchange,提问作者Ali Daghayeghi
相关产品推荐
相关产品推荐

