swiftconcurrencysemaphoredispatchgroup

Swift DispatchGroup notify called before task finish


I use semaphore and DispatchGroup deal with the concurrency network.It's working fine in the demo.But it's not working after I copy the code to the project.Can anyone find the problem?

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)
    }

and the demo result:

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

project:

 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")
            }
            
        }
        
    }

and the project result:

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

I can't find the way deal with the problem cause the demo code working fine. Does anybody can find the reason to work out this?


Solution

  • You have several issues. Your demo doesn't actually simulate your real code because your demo getRequestWithCallback isn't actually asynchronous like the call to MCNetwork.share.request is. If you made getRequestWithCallback to be asynchronous then you would find that your demo would fail like your real code.

    The first issue is that your semaphore isn't doing anything so you may as well remove all use of the semaphore.

    The second, and more important, issue is that you should not use async to call asynchronous code like you do in your real code. The reason the real code fails is because the loop completes immediately and all calls to async complete long before the calls to MCNetwork.share.request have a chance to run. In the demo, since getRequestWithCallback isn't asynchronous, the calls to async don't complete until getRequestWithCallback returns (after the sleep).

    Here's one way to refactor your real code:

    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 group = DispatchGroup()
        
        queue.async {
            for i in 0..<params.count {
                para = params[i] as! [String : Any]
                
                group.enter()
                MCNetwork.share.request(url: url, Method: .get,Para: para,encoding:URLEncoding.default) {  obj in
                    print("success=\(i)")
                    group.leave()
                } RError: { err in
                    print("error=\(i)")
                    group.leave()
                }
            }
            
            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")
            }
        }
    }
    

    This will queue up all 10 network calls concurrently. The calls to enter and leave keep track of how many active async network calls there are. The code inside the group.notify block will be called after all 10 network calls complete (or error).