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") } } }
额外注意点
- 当
stop为true时,必须调用semaphore.signal(),否则信号量计数会一直被占用,导致后续任务无法执行甚至死锁。 - 避免强制解包
params[i],增加安全判断防止崩溃。 - 原代码中
queue.async(group: group)的方式只适用于闭包内的同步任务,异步任务必须用enter/leave手动管理。
内容的提问来源于stack exchange,提问作者LCSome
相关产品推荐
相关产品推荐

