Task.WaitAll聚合异常处理:批量API请求成功结果处理遇异常
问题:批量处理异步任务时遍历触发未捕获AggregateException
我开发了一个按100条为一批处理结果的程序,每条数据单独发起API请求后批量处理返回结果。近期调用的API出现扩容问题,约10%的请求会随机失败或超时。
我希望仅处理请求成功的结果,但在遍历Task数组时,会触发新的AggregateException(其InnerExceptions仅包含一个异常),仿佛触碰到了Task数组中的“定时炸弹”成员,却无法定位原因。
外层异常捕获逻辑:
try { GetBatchStats(); } catch (AggregateException e) { var firstE = e.InnerExceptions[0]; _logger.Fatal($"{e.InnerExceptions.Count.ToString()} api requests threw exceptions. Sample exception: {firstE.Message}\nStackTrace: {firstE.StackTrace}"); }
最初的批量处理逻辑:
int asyncExceptions = 0; try { Task.WaitAll(batchRequests); } catch(AggregateException e) { asyncExceptions = e.InnerExceptions.Count; var firstE = e.InnerExceptions[0]; _logger.Error($"{asyncExceptions.ToString()} api requests out of {batchRequests.Length.ToString()} threw exceptions. Sample exception: {firstE.Message}\nStackTrace: {firstE.StackTrace}"); } if (asyncExceptions == batchRequests.Length) { // All requests in this batch errored out; api must be down throw new Exception("All api requests in batch returned errors. Must be a problem with the api."); } int countErrs = 0; foreach (var apiResult in batchRequests) { if (apiResult.Exception != null || apiResult.Result == null) countErrs++; if (apiResult.Exception == null && apiResult.Result != null) { ... } }
日志显示先输出“Y条请求中有X条抛出异常”的错误,紧接着出现“1条请求抛出异常”的致命信息,说明:
- 程序进入了处理成功结果的循环;
- 访问Task数组中的某个成员时触发了新的AggregateException,但无法定位原因。
改用Task.Status判断后问题依旧:
int asyncExceptions = 0; try { Task.WaitAll(batchRequests); } catch(AggregateException e) { asyncExceptions = e.InnerExceptions.Count; var firstE = e.InnerExceptions[0]; _logger.Error($"{asyncExceptions.ToString()} api requests out of {batchRequests.Length.ToString()} threw exceptions. Sample exception: {firstE.Message}\nStackTrace: {firstE.StackTrace}"); } if (asyncExceptions == batchRequests.Length) { // All requests in this batch errored out; api must be down throw new Exception("All api requests in batch returned errors. Must be a problem with the api."); } int countErrs = 0; foreach (var apiResult in batchRequests) { if (apiResult.Status == TaskStatus.Faulted || apiResult.Result == null) countErrs++; if (apiResult.Status == TaskStatus.RanToCompletion && apiResult.Result != null) { ... } }
求问:如何正确处理一组任务中成功的结果,同时跳过错误的任务?
解决方案
问题核心是:当任务处于Faulted状态时,访问其Result属性会自动抛出该任务的异常——哪怕你已经通过Task.WaitAll捕获过一次AggregateException,后续访问Faulted任务的Result依然会触发未捕获的异常。
修正同步处理逻辑
先通过Task.Status判断任务状态,仅对RanToCompletion状态的任务访问Result,直接跳过错误任务:
int asyncExceptions = 0; try { Task.WaitAll(batchRequests); } catch(AggregateException e) { asyncExceptions = e.InnerExceptions.Count; var firstE = e.InnerExceptions[0]; _logger.Error($"{asyncExceptions} api requests out of {batchRequests.Length} threw exceptions. Sample exception: {firstE.Message}\nStackTrace: {firstE.StackTrace}"); } if (asyncExceptions == batchRequests.Length) { throw new Exception("All api requests in batch returned errors. Must be a problem with the api."); } int countErrs = 0; foreach (var apiResult in batchRequests) { // 先判断任务状态,避免访问Result触发异常 if (apiResult.Status != TaskStatus.RanToCompletion) { countErrs++; continue; } // 仅处理成功完成的任务 var result = apiResult.Result; if (result == null) { countErrs++; continue; } // 这里写处理result的业务逻辑 // ... }
推荐:改用异步/await模式
异步模式的异常处理更直观,每个任务的异常可单独捕获,不会影响其他任务:
// 为每个任务单独包裹异常捕获逻辑 var wrappedTasks = batchRequests.Select(async task => { try { var result = await task; return (IsSuccess: true, Result: result); } catch (Exception ex) { _logger.Error($"API请求失败: {ex.Message}\nStackTrace: {ex.StackTrace}"); return (IsSuccess: false, Result: default); } }).ToList(); // 等待所有任务完成,无论成功失败 var results = await Task.WhenAll(wrappedTasks); int countErrs = 0; foreach (var res in results) { if (!res.IsSuccess || res.Result == null) { countErrs++; continue; } // 处理成功的结果 // ... } if (countErrs == batchRequests.Length) { throw new Exception("All api requests in batch returned errors. Must be a problem with the api."); }
这种方式下,遍历结果时不会触发意外异常,代码可读性和可维护性更强。
内容的提问来源于stack exchange,提问作者user1664043
相关产品推荐
相关产品推荐

