Tyrus Rechs
Tyrus Rechs

Reputation: 73

Swift: Simple DispatchQueue does not run & notify correctly

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:

ios-Simulator playground

I don't get it. ๐Ÿ˜…

Upvotes: 2

Views: 2388

Answers (2)

Rob
Rob

Reputation: 437582

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.

Upvotes: 1

David Pasztor
David Pasztor

Reputation: 54716

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

Upvotes: 3

Related Questions