Swift中如何等待多个并行异步调用全部完成后返回结果
并行批量保存实现方案
你可以根据需求选择以下两种实现方式,两种都能满足「所有任务并行执行、全部完成后返回结果」的要求。
方案1:ThrowingTaskGroup 实现(动态任务量首选)
这是Swift并发里处理动态数量并行任务的标准实现,自带错误传递、自动取消子任务的能力,是这类场景的首选方案:
static func save(files: [URL]) async throws -> [String] { try await withThrowingTaskGroup(of: (Int, String).self) { group in // 遍历文件时给每个任务绑定输入索引,保证最终结果顺序和输入一致 for (index, fileURL) in files.enumerated() { group.addTask { let remoteURL = try await save(fileURL) return (index, remoteURL) } } // 初始化定长占位数组 var resultList = Array<String?>(repeating: nil, count: files.count) // 逐次接收完成的任务结果,按索引放到对应位置 for try await (index, remoteURL) in group { resultList[index] = remoteURL } // 所有任务完成后数组所有位置都有值,直接转换返回即可 return resultList.map { $0! } } }
这个方案的特性:
- 所有保存任务完全并行执行,总耗时取决于最慢的单个保存任务,而非所有任务耗时累加
- 任意一个任务抛出错误时,TaskGroup会自动取消所有其他正在运行的子任务,立即向上抛出错误,符合throwing函数的错误传播语义
- 索引绑定的写法避免了任务完成先后导致的结果乱序问题,返回数组顺序和输入
files数组完全对应
方案2:Task数组实现(轻量场景适用)
如果任务量不大,也可以手动创建Task实例存入数组,再统一等待结果返回,写法更简洁:
static func save(files: [URL]) async throws -> [String] { // 注意:必须先创建完所有Task实例,再统一await结果,否则会变成串行执行 let tasks = files.map { fileURL in Task { try await save(fileURL) } } var resultList = [String]() // 按输入顺序依次获取每个任务的结果 for task in tasks { resultList.append(try await task.value) } return resultList }
这个方案的注意点:
- 禁止在
map闭包内直接await save(fileURL),否则会变成逐个串行执行,完全失去并行效果 - 错误发生时不会自动取消其他已启动的任务,未完成的任务会继续在后台执行,如果你需要任务级联取消的能力,优先选择TaskGroup方案
- 天然保证返回结果顺序和输入顺序一致
常见踩坑:不要写
for url in files { results.append(try await save(url)) }这类循环内直接await的代码,这种写法是严格串行的,总耗时是所有文件保存耗时的总和。
内容的提问来源于stack exchange,提问作者Dannis Case
相关产品推荐
相关产品推荐

