不使用async/await实现多任务并行并等待全部完成的最优方案
现有代码的可运行性说明
你当前的写法确实可以实现两个任务并行执行,且等待两个任务完成后再执行Pack逻辑,在没有同步上下文的运行环境(比如.NET Core/.NET 5+控制台程序、ASP.NET Core服务)下可以正常运行,但存在明显的隐患和优化空间。
现有实现的潜在问题
- 死锁风险:直接调用
Task.Result会阻塞当前线程等待任务返回,如果你的代码运行在有同步上下文的环境(比如WPF、WinForms、旧版ASP.NET Framework)中,会触发死锁:任务执行完成后需要回到原同步上下文继续执行,但同步上下文的线程已经被.Result阻塞,导致双方互相等待。 - 异常处理不友好:如果任意一个任务执行抛出异常,
.Result会直接抛出包装后的AggregateException,你无法直接区分是哪个任务出错,也无法在异常抛出后做针对性的清理操作。 - 等待逻辑冗余:你当前是在获取第一个Result时阻塞等待第一个任务,获取第二个Result时再阻塞等待第二个任务,虽然不影响最终结果,但不如统一等待两个任务完成的逻辑清晰。
更优的实现方案
核心优化点是:消除同步上下文捕获、统一等待所有任务、优化异常处理。
优化后的代码示例:
public string Export() { var ifcFilePaths = ExportIfcFiles().ToList(); // 启动两个并行任务 Task<List<IfcToCsvGraphExportResult>> graphConverterResults = ConvertIfcToGraphData(ifcFilePaths); Task<List<IfcToGltfConversionResult>> gltfConverterResults = ConvertIfcToGltfData(ifcFilePaths); try { // 统一等待两个任务全部完成 Task.WaitAll(graphConverterResults, gltfConverterResults); } catch (AggregateException ex) { // 可以展开内层异常,也可以单独判断每个任务的状态做针对性处理 foreach (var innerEx in ex.Flatten().InnerExceptions) { // 按需处理异常,比如打日志、清理临时文件等 } throw; // 或者返回自定义错误标识 } List<string> folders = _outputFolderPacker.Pack(graphConverterResults.Result, gltfConverterResults.Result).ToList(); return _zipPacker.Pack(folders); } private Task<List<IfcToCsvGraphExportResult>> ConvertIfcToGraphData(List<string> ifcFilePaths) => Task.Run(() => _ifcToCsvGraphExporter.Export(ifcFilePaths).ToList()) .ConfigureAwait(false); // 配置不捕获同步上下文,大幅降低死锁风险 private Task<List<IfcToGltfConversionResult>> ConvertIfcToGltfData(List<string> ifcFilePaths) => Task.Run(() => _ifcToGltfConverter.Convert(ifcFilePaths).ToList()) .ConfigureAwait(false); // 配置不捕获同步上下文
额外优化建议
如果你的两个转换操作是IO密集型(比如读写大IFC文件、调用外部转换工具),可以进一步把_ifcToCsvGraphExporter.Export和_ifcToGltfConverter.Convert改为异步实现,就算Export方法不能改为async,也能减少线程资源的浪费。如果是CPU密集型操作,当前的Task.Run实现已经是合理选择。
内容的提问来源于stack exchange,提问作者Rade Tomovic
相关产品推荐
相关产品推荐

