Parallel.ForEach结合async委托仅处理首行,无法遍历后续行
Parallel.ForEach仅处理首行数据的问题
问题代码
List<customerModel> rowResults = DataFromCustomersTable(); ParallelLoopResult ploopResult = Parallel.ForEach(rowResults, po, async row => { // 检查客户是否有默认配置文件 ResponseResultModel response = await CustomerDefaultSourceTest(row); // 用上述测试结果更新表格 string message = await custData.UpdateResult(row, response); }); // 循环结束
CustomerDefaultSourceTest和UpdateResult方法均包含try/catch块,即使移除循环内的try/catch,仍只有首行会被处理。
问题原因
Parallel.ForEach不支持异步委托。你传入的async row => ...会被解析为async void方法,这种类型的方法无法被Parallel.ForEach跟踪执行状态——Parallel会认为每个委托任务已经完成,直接继续后续流程,而实际的异步操作还在后台运行。如果程序在这些异步任务完成前就退出,就会出现只有首行(或少数行)被处理的假象。
另外,async void的异常处理逻辑和普通异步方法不同,即使内部有try/catch,也可能因为线程上下文问题导致意外的流程终止。
解决方案
方案1:改用Task.WhenAll(推荐,适合IO密集型场景)
你的代码是典型的IO密集型操作(调用异步方法测试、更新数据),不需要用Parallel做CPU并行,直接用Task的批量等待即可,这是异步场景下的正确处理方式:
List<customerModel> rowResults = DataFromCustomersTable(); // 生成所有异步任务 var tasks = rowResults.Select(async row => { ResponseResultModel response = await CustomerDefaultSourceTest(row); await custData.UpdateResult(row, response); }); // 等待所有任务完成 await Task.WhenAll(tasks);
方案2:强制同步等待(不推荐,仅适用于必须用Parallel的场景)
如果你的业务场景确实需要CPU并行(比如存在大量计算逻辑),可以在委托内同步等待异步方法完成,但这样会失去异步IO的优势,还可能导致线程池资源耗尽:
ParallelLoopResult ploopResult = Parallel.ForEach(rowResults, po, row => { ResponseResultModel response = CustomerDefaultSourceTest(row).GetAwaiter().GetResult(); string message = custData.UpdateResult(row, response).GetAwaiter().GetResult(); });
内容的提问来源于stack exchange,提问作者vambat
相关产品推荐
相关产品推荐

