Swift并行任务进度条无法实时更新问题及排查尝试
问题分析与解决方案
核心问题
- 主线程阻塞:
DispatchQueue.concurrentPerform是同步执行方法,你在onAppear(主线程)中直接调用inParallell,导致主线程被完全阻塞。所有DispatchQueue.main.async的进度更新任务都会被积压,直到所有并行任务执行完毕、主线程恢复空闲后才会批量执行,这就是进度条最后一次性更新的原因。 - 进度计算逻辑错误:代码中
meter += 1.0 / step是错误的,step = 1.0 / count,所以1.0/step等于count。以count=10为例,每次任务完成后进度直接增加10,导致meter瞬间跳到10、20…100,这就是打印信息全是100.0的原因。
修正方案
- 将
inParallell的执行逻辑放到后台线程,彻底避免阻塞主线程。 - 修正进度计算,每次任务完成后进度增加
1.0 / Double(count)的比例。 - 优化锁的使用范围,减少锁的持有时间,提升并行执行效率。
修正后的代码
func randomIndices(n: Int) -> [Int] { var result: [Int] = [] var used: [Bool] = (0 ..< n).map { _ in false } for _ in 0 ..< n { var r = Int.random(in: 0 ..< n) while used[r] { r = (r + 1) % n } used[r] = true result.append(r) } return result } func split<R>(array: [R], n: Int) -> [[R]] { var result: [[R]] = (0 ..< n).map { _ in [] } var index = 0 for item in array { result[index].append(item) index = (index + 1) % n } return result } func inParallell<R>(count: Int, n: Int = 4, function: @escaping (Int) -> R, progress: ((Double) -> Void)? = nil) { // 后台线程执行并行任务,避免阻塞主线程 DispatchQueue.global(qos: .utility).async { let indices = split(array: randomIndices(n: count), n: n) var res: [R?] = .init(repeating: nil, count: count) let lock = NSRecursiveLock() var meter = 0.0 let step = 1.0 / Double(count) DispatchQueue.concurrentPerform(iterations: indices.count) { i in for index in indices[i] { let result = function(index) lock.lock() res[index] = result meter += step let currentProgress = meter // 复制当前进度,避免锁持有期间执行主线程操作 lock.unlock() // 锁外发送主线程更新,减少锁的持有时间 DispatchQueue.main.async { print("inParallell meter \(currentProgress)") progress?(currentProgress) } } } DispatchQueue.main.async { progress?(1.0) } // 如果需要返回任务结果,建议添加completion回调替代同步返回 // 示例:completion?(res.compactMap { $0 }) } } func waitSomeTime() { var x = 0 for i in 0 ..< 1_000_000 { x += i } } struct InParallellTestView: View { @State var progress = 0.0 var body: some View { ProgressView(value: progress) .onAppear { inParallell(count: 10, function: { _ in waitSomeTime() }, progress: { p in self.progress = p }) } } }
额外说明
- 原代码中
inParallell同步返回结果的设计会阻塞调用线程,改成异步回调(如需返回结果)更符合iOS多线程最佳实践。 - 使用
DispatchQueue.global(qos: .utility)执行计算密集型任务,既保证执行效率,又不会抢占UI线程资源。 - 锁仅保护
res数组和meter进度值的修改,主线程更新操作放在锁外,减少锁的持有时间,提升并行任务的执行效率。
内容的提问来源于stack exchange,提问作者Samuel Cederlund
相关产品推荐
相关产品推荐

