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

Swift中DispatchGroup notify提前触发问题排查求助

并发网络请求中DispatchGroup.notify提前触发问题排查

我用Semaphore和DispatchGroup处理并发网络请求,Demo代码运行正常,但复制到项目后出现异常——DispatchGroup的notify方法在网络任务完成前就被触发了。以下是相关代码及运行结果,恳请帮忙排查问题原因。

Demo代码

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    
    let queue = DispatchQueue.global(qos: .default)
    let semaphore = DispatchSemaphore(value: 10)
    let group = DispatchGroup()
    
    var stop = false
    
    queue.async {
        for i in 0..<10 {
            semaphore.wait()
            queue.async(group: group, execute: { [self] in
                if stop {
                    return
                }
                
                print("beigin = \(i)")
                getRequestWithCallback { Bool in
                    print("end = \(i)")
                    semaphore.signal()
                    
                } error: { Bool in
                    print("end = \(i)")
                    stop = true
                    semaphore.signal()
                }
            })
        }
        
        group.notify(queue: queue) {
            print("finish")
        }
    }
}

func getRequestWithCallback(success:successCallBack, error:errorCallBack){
    sleep(2)
    success(true)
}

Demo运行结果

beigin = 8
beigin = 3
beigin = 7
beigin = 0
beigin = 4
beigin = 2
beigin = 9
beigin = 5
beigin = 6
beigin = 1
end = 8
end = 2
end = 5
end = 7
end = 3
end = 0
end = 4
end = 1
end = 6
end = 9
finish

项目代码

func uploadEventTrackingFiles()  {
    let array_all = UserDefaults.standard.array(forKey: "eventArray") ?? Array()
    let params = array_all.prefix(10)
    var para = [String : Any]()
  
    let url = "https://dc-o.api.leiniao.com/frontreport/custom"
    
    MCNetwork.share.headers = ["Content-Type": "application/json"]
    
    let queue = DispatchQueue.global(qos: .default)
    let semaphore = DispatchSemaphore(value: 10)
    let group = DispatchGroup()
    
    var stop = false
    
    queue.async {
        for i in 0..<params.count{
            semaphore.wait()
            queue.async(group: group,  execute: {
                if(stop){
                    return
                }
                
                para = params[i] as! [String : Any]
                
                MCNetwork.share.request(url: url, Method: .get,Para: para,encoding:URLEncoding.default) {  obj in
                    print("successs=\(i)")
                    semaphore.signal()
                }RError: { err in
                    stop = true
                    print("error=\(i)")
                    semaphore.signal()
                }
            })
        }
        
        group.notify(queue: queue) {
            var array_all = UserDefaults.standard.array(forKey: "eventArray") ?? Array()
            array_all.removeSubrange(0..<10)
            UserDefaults.standard.setValue(array_all, forKey: "eventArray")
            print("finish")
        }
    }
}

项目运行结果

finish
successs=2
successs=1
successs=0
successs=3
successs=5
successs=4
successs=7
successs=6
successs=8
successs=9

问题原因

核心是DispatchGroup的计数逻辑被异步任务打破:

  • Demo里的getRequestWithCallback虽然用了回调,但sleep(2)是同步执行的,整个queue.async(group: group)的闭包要等sleep和回调执行完才会结束,DispatchGroup会正确计数,直到所有任务完成才触发notify。
  • 项目中的MCNetwork.share.request是真正的异步网络请求:queue.async(group: group)的闭包在发起请求后就直接结束了,DispatchGroup会认为这个任务已经完成,计数直接减1。当10个发起请求的闭包都执行完,DispatchGroup计数归零,notify就提前触发了,此时网络请求还在后台运行,远没走到回调里的semaphore.signal()。

解决方案

必须手动管理DispatchGroup的进入和离开,确保它等待到网络请求的回调完成:

func uploadEventTrackingFiles()  {
    let array_all = UserDefaults.standard.array(forKey: "eventArray") ?? Array()
    let params = array_all.prefix(10)
    
    let url = "https://dc-o.api.leiniao.com/frontreport/custom"
    MCNetwork.share.headers = ["Content-Type": "application/json"]
    
    let queue = DispatchQueue.global(qos: .default)
    let semaphore = DispatchSemaphore(value: 10)
    let group = DispatchGroup()
    
    var stop = false
    
    queue.async {
        for i in 0..<params.count {
            semaphore.wait()
            queue.async(execute: {
                guard !stop else {
                    semaphore.signal() // 释放信号量,避免死锁
                    return
                }
                
                group.enter() // 手动标记任务开始
                guard let para = params[i] as? [String : Any] else {
                    semaphore.signal()
                    group.leave()
                    return
                }
                
                MCNetwork.share.request(url: url, Method: .get, Para: para, encoding: URLEncoding.default) { obj in
                    print("successs=\(i)")
                    semaphore.signal()
                    group.leave() // 任务完成,标记离开
                } RError: { err in
                    stop = true
                    print("error=\(i)")
                    semaphore.signal()
                    group.leave() // 错误场景也要标记离开
                }
            })
        }
        
        group.notify(queue: queue) {
            var array_all = UserDefaults.standard.array(forKey: "eventArray") ?? Array()
            if array_all.count >= 10 {
                array_all.removeSubrange(0..<10)
                UserDefaults.standard.setValue(array_all, forKey: "eventArray")
            }
            print("finish")
        }
    }
}

额外注意点

  1. 当stop为true时,必须调用semaphore.signal(),否则信号量计数会一直被占用,导致后续任务无法执行甚至死锁。
  2. 避免强制解包params[i],增加安全判断防止崩溃。
  3. 原代码中queue.async(group: group)的方式只适用于闭包内的同步任务,异步任务必须用enter/leave手动管理。

内容的提问来源于stack exchange,提问作者LCSome

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 02:16:00