Swift: Simple DispatchQueue does not run & notify correctly

核能气质少年 提交于 2020-05-29 16:59:25

问题


What i am doing wrong? At playground it runs as it should. But as soon as i deploy it on iOS simulator it returns the wrong sequence.

@objc func buttonTapped(){

    let group = DispatchGroup()
    let dispatchQueue = DispatchQueue.global(qos: .default)

    for i in 1...4 {
        group.enter()
        dispatchQueue.async {
            print("🔹 \(i)")  
        }
        group.leave()
    }

    for i in 1...4 {
        group.enter()
        dispatchQueue.async {
            print("❌ \(i)")
        }
        group.leave()
    }

    group.notify(queue: DispatchQueue.main) {
        print("jobs done by group")
    }   
}

Console Output:

I don't get it. 😅


回答1:


You should put the group.leave() statement in the dispatchQueue.async block as well, otherwise it will be executed synchronously before the async block would finish execution.

@objc func buttonTapped(){

    let group = DispatchGroup()
    let dispatchQueue = DispatchQueue.global(qos: .default)

    for i in 1...4 {
        group.enter()
        dispatchQueue.async {
            print("🔹 \(i)")  
            group.leave()
        }
    }

    for i in 1...4 {
        group.enter()
        dispatchQueue.async {
            print("❌ \(i)")
            group.leave()
        }
    }

    group.notify(queue: DispatchQueue.main) {
        print("jobs done by group")
    }   
}



回答2:


As Dávid said, properly employed dispatch groups only ensure that the notification takes place after all of the tasks finish, which you can achieve by calling leave from within the dispatched blocks, as he showed you. Or alternatively, since your dispatched tasks are, themselves, synchronous, you don't have to manually enter and leave the group, but can use group parameter of async method:

let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .default)

for i in 1...4 {
    queue.async(group: group) {
        print("🔹 \(i)")
    }
}

for i in 1...4 {
    queue.async(group: group) {
        print("❌ \(i)")
    }
}

group.notify(queue: .main) {
    print("jobs done by group")
}

Use group.enter() and group.leave() when calling some asynchronous method, but in the case of these print statements, you can just use async(group:execute:) as shown above.

Now, we've solved the problem where the "jobs done by group" block didn't wait for all of the dispatched tasks. But, because you're doing all of this dispatching to a concurrent queue (all the global queues are concurrent queues), you have no assurances that your tasks will be performed in the order that you requested. They're queued up in a strict FIFO manner, but because they're concurrent, you have no assurances when you'll hit the respective print statements.

If you need it to print the messages in order, you will have to use a serial queue. For example, if you create your own queue, in the absence of the .concurrent attribute, the following will create a serial queue:

// create serial queue

let queue = DispatchQueue(label: "...")

// but not your own concurrent queue:
//
// let queue = DispatchQueue(label: "...", attributes: .concurrent)
//
// nor one of the global concurrent queues:
//
// let queue = DispatchQueue.global(qos: .default)
//

And if you run the above code with this serial queue, you'll see what you were looking for:

🔹 1
🔹 2
🔹 3
🔹 4
❌ 1
❌ 2
❌ 3
❌ 4
jobs done by group

But, then, again, if you were using a serial queue, the group would be completely unnecessary (you could just add the "completion" task as yet another dispatched task at the end of the serial queue). I only show the use of serial queues as a way to avoid the race condition of dispatching eight tasks to a concurrent queue.



来源:https://stackoverflow.com/questions/53543334/swift-simple-dispatchqueue-does-not-run-notify-correctly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!