ASP.NET MVC多API调用并行优化及异步异常问题咨询
ASP.NET MVC多供应商API调用异步优化方案
问题背景
对接10余家供应商API获取航班数据,原代码使用Task.WaitAll阻塞等待所有请求完成,超时导致用户体验差;尝试Task.WhenAny后出现视图无数据、错误未捕获的问题,需优化代码逻辑。
原代码核心问题
- 主线程阻塞:
Task.WaitAll会阻塞当前线程,长时间等待引发请求超时,用户等待体验极差。 - 线程不安全集合:
List<FlightDetail>非线程安全,多线程并发添加数据会导致数据丢失、异常,最终视图无数据。 - 异常处理缺失:仅捕获
OperationCanceledException,API调用失败、网络错误等异常未处理,任务失败无记录,数据完整性无法保障。 - 参数共享风险:循环中复用
copyRequest,后续任务可能拿到被修改后的请求参数,导致请求逻辑错误。 - 无单任务超时机制:单个供应商API响应慢会拖慢整体流程,没有超时熔断控制。
改进方案
关键优化点
- 改用
async/await非阻塞模式,替换Task.WaitAll为await Task.WhenAll,释放线程池资源。 - 使用
ConcurrentBag<FlightDetail>线程安全集合存储结果,避免并发修改冲突。 - 为每个API调用添加独立超时控制,超时自动取消任务并记录日志。
- 全量捕获任务异常,单个任务失败不影响其他任务执行。
- 循环内为每个任务创建独立的请求参数副本,避免参数共享导致的逻辑错误。
- 用
Task.Run替代Task.Factory.StartNew,符合现代异步编程规范。
改进后完整代码
public async Task<List<string>> ApplySourcesAsync(List<string> sources, string suppliers, List<RNR> lstRnr, FlightSearchRequest rq, Client client, List<FlightDetail> lstflightdetails, List<PccMatrix> pccMatrixList, bool isdomesticreturn = false) { List<string> lstsources = new List<string>(); SortedDictionary<string, Airport> airports = _flyCache.Airports; List<Task> taskList = new List<Task>(); // 线程安全集合存储多任务结果 ConcurrentBag<FlightDetail> flightDetails = new ConcurrentBag<FlightDetail>(); FLY.dal.Flight.Handlers.NearbyAirportsHandler nearbyAirportsHandler = new dal.Flight.Handlers.NearbyAirportsHandler(); List<entities.Flight.NearbyAirports> nearbyAirports = nearbyAirportsHandler.GetNearbyAirportsActive(); string[] flyapisources = new string[] { "FHIN", "FHBD", "FHAE", "FHTR", "FHMY", "FHSG" }; foreach (var source in sources) { // 每个任务创建独立请求副本,避免参数共享修改 FlightSearchRequest copyRequest = (FlightSearchRequest)FLY.Helper.Helper.DeepCloneusingJson(rq); if (string.IsNullOrWhiteSpace(rq.AirTravelPreference.AirlinePreference) && !string.IsNullOrWhiteSpace(copyRequest.AirTravelPreference.AirlinePreference)) { copyRequest.AirTravelPreference.AirlinePreference = ""; } IFlightsSuppliers supplier = null; if (!rq.IsBSAdvancedSearch) { if (source == "MY" && suppliers.Contains("MY")) { List<RNR> lstRNRsource = lstRnr.Where(a => a.Source == "ALL" || a.Source == "MY").ToList(); if (RNRToAllowSearch(lstRNRsource, airports, copyRequest)) supplier = new MyFares.Mystifly(); } if (source == "2S" && suppliers.Contains("2S")) { List<RNR> lstRNRsource = lstRnr.Where(a => a.Source == "ALL" || a.Source == "2S").ToList(); if (RNRToAllowSearch(lstRNRsource, airports, copyRequest)) supplier = new SabreRest.Flights(); } if (source == "1S" && suppliers.Contains("1S")) { List<RNR> lstRNRsource = lstRnr.Where(a => a.Source == "ALL" || a.Source == "1S").ToList(); if (RNRToAllowSearch(lstRNRsource, airports, copyRequest, pccMatrixList)) { var filterednearbyairports = nearbyAirports?.Where(a => a.Source.Equals("1S")).ToList(); supplier = new Sabre.SabreAPI(); } } // Singapore if (source == "SQ" && suppliers.Contains("SQ")) { List<RNR> lstRNRsource = lstRnr.Where(a => a.Source == "ALL" || a.Source == "SQ").ToList(); bool airlineAllowed = string.IsNullOrWhiteSpace(copyRequest.AirTravelPreference.AirlinePreference) || copyRequest.AirTravelPreference.AirlinePreference.ToUpper().Split(',').Contains("SQ"); if (RNRToAllowSearch(lstRNRsource, airports, copyRequest) && airlineAllowed) supplier = new SingaporeAirline.SingaporeAirlineAPI(); } } if (supplier != null) { lstsources.Add(source); var newpccMatrixList = pccMatrixList.Where(a => a.IsAvailable).ToList(); if (source.StartsWith("FH")) { newpccMatrixList = newpccMatrixList.Where(a => a.Pcc == source).ToList(); } var copiednearbyairports = nearbyAirports?.Where(a => a.Source.Equals(source)).ToList(); if (copiednearbyairports != null) copiednearbyairports = FLY.Helper.Helper.DeepCloneusingJson(copiednearbyairports); // 带超时控制的异步任务 var task = Task.Run(async () => { using (var cts = new CancellationTokenSource()) { // 单个任务超时设置为30秒,可根据实际调整 var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30), cts.Token); var apiTask = Task.Run(() => { List<FlightDetail> tempDetails = new List<FlightDetail>(); supplier.Search(copyRequest, tempDetails, newpccMatrixList, client, copiednearbyairports); // 将临时结果添加到线程安全集合 foreach (var detail in tempDetails) { flightDetails.Add(detail); } }, cts.Token); var completedTask = await Task.WhenAny(apiTask, timeoutTask); if (completedTask == timeoutTask) { cts.Cancel(); log.Error($"供应商[{source}] API调用超时"); } else { await apiTask; // 确保API任务完成,捕获执行异常 } } }); taskList.Add(task); pccMatrixList.ForEach(a => a.IsAvailable = false); } } try { if (taskList.Count > 0) await Task.WhenAll(taskList); // 非阻塞等待所有任务完成 } catch (Exception ex) { log.Error($"批量API调用异常: {ex.Message}\n{ex.StackTrace}"); } var resultFlightDetails = flightDetails.ToList(); if (resultFlightDetails.Count == 0) return lstsources; if (!iscouponexecuted) flightBookingCouponDetails = new FlightBookCouponCodeProvider().GetBookingCoupons(rq, airports); var currentrequest = FLY.Helper.Helper.DeepCloneusingJson(rq); resultFlightDetails.ForEach(a => { a.FlightSearchRequest = currentrequest; a.BookingCouponDetails = flightBookingCouponDetails; }); lstflightdetails.AddRange(resultFlightDetails); return lstsources; }
优化效果说明
- 非阻塞异步:
await Task.WhenAll释放线程池资源,提升系统并发能力,避免用户长时间等待。 - 数据安全:
ConcurrentBag确保多线程环境下数据无丢失、无冲突,解决视图无数据问题。 - 超时控制:单个API任务超时自动取消,避免慢接口拖垮整体流程。
- 异常可控:全量捕获异常,单个任务失败不影响其他任务,日志记录便于问题排查。
- 参数隔离:每个任务使用独立请求副本,避免参数共享导致的逻辑错误。
内容的提问来源于stack exchange,提问作者Sagar Pujar
相关产品推荐
相关产品推荐

