You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift并行任务进度条无法实时更新问题及排查尝试

问题分析与解决方案

核心问题

  1. 主线程阻塞:DispatchQueue.concurrentPerform是同步执行方法,你在onAppear(主线程)中直接调用inParallell,导致主线程被完全阻塞。所有DispatchQueue.main.async的进度更新任务都会被积压,直到所有并行任务执行完毕、主线程恢复空闲后才会批量执行,这就是进度条最后一次性更新的原因。
  2. 进度计算逻辑错误:代码中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 06:02:15