8

I'm using DispatchGroup to perform a task, but group.notify is being called before the task is completed.

My code:

let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")
queueImage.async(group: group) {
    sleep(2)
    print("image")
}

queueVideo.async(group: group) {
    sleep(3)
    print("video")
}

group.notify(queue: .main) {
    print("all finished.")
}

Logs:

all finish.
image
video
Victor
  • 109
  • 1
  • 2
  • 11

3 Answers3

19

Update: The question above actually runs correctly as is (as rmaddy pointed out!)

I'm saving this wrong answer below in case others get confused about DispatchQueue's async(group:) methods behavior, since Apple's swift doc on it is currently lousy.


The group's enter() needs to be called before each call to async(), and then the group's leave() needs to be called at end of each async() block, but within the block. It's basically like a refcount that when it reaches zero (no enters remaining), then the notify block is called.

let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")

group.enter()
queueImage.async(group: group) {
    sleep(2)
    print("image")
    group.leave()
}

group.enter()
queueVideo.async(group: group) {
    sleep(3)
    print("video")
    group.leave()
}

group.notify(queue: .main) {
    print("all finished.")
}
Smartcat
  • 2,834
  • 1
  • 13
  • 25
  • 2
    Then what is the point of the `group` parameter in the calls to `async`? That usage should preclude the need for calls to `enter` and `leave`. – rmaddy Mar 20 '18 at 04:21
  • @rmaddy I couldn't find any docs or examples to support what you said, but I went ahead and tried it (removing the enter and leave lines completely) and it worked. Will keep looking for docs... curious. Will withdraw my answer. – Smartcat Mar 20 '18 at 04:34
  • Read the corresponding Objective-C documentation. It has more details. – rmaddy Mar 20 '18 at 04:34
  • I suppose [this Apple doc](https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW25) covers it enough to be believable. Thanks rmaddy! – Smartcat Mar 20 '18 at 04:43
  • @Victor Why did you accept this answer? No offense to Smartcat but this answer isn't correct because the code in your question works as-is. There is no need to use the calls to `enter` and `leave`. – rmaddy Mar 20 '18 at 05:27
  • @rmaddy The update at the top of the answer is correct, and it's the only posted answer, so I guess he was happy enough with it. I, for one, would feel better if you posted your own answer (you figured it out, after all!), and then Victor could re-accept to your answer, as [this meta SO](https://meta.stackexchange.com/a/93970) advises. Cheers! – Smartcat Mar 20 '18 at 05:40
  • 1
    But answers that state "your code works" are not considered acceptable answers on SO. There's nothing to post. The question actually should be closed as an unreproducible issue. – rmaddy Mar 20 '18 at 05:41
  • When I run the *original* code in the `repl` it does sort of work .. except it does not actually print anything. note that the sleep's _are_ working – WestCoastProjects May 18 '20 at 22:41
  • @Smartcat I was calling group.enter() inside an async block .sometime it works appropriately and sometimes it runs notify() before even the block start execution ..Thank You for the explanation this solved my problem :) – Mohammad Azam Sep 22 '20 at 07:47
1

Generic answer : (Swift 5)

let yourDispatchGroup = DispatchGroup()

yourDispatchGroup.enter()
task1FunctionCall {
  yourDispatchGroup.leave() //task 1 complete
}

yourDispatchGroup.enter()
task2FunctionCall {
  yourDispatchGroup.leave() //task 2 complete
}

.. ..
yourDispatchGroup.enter()
tasknFunctionCall {
  yourDispatchGroup.leave() //task n complete
}

dispatchGroup.notify(queue: .main) {
  //This is invoked when all the tasks in the group is completed.
}
iPhoneDeveloper
  • 958
  • 1
  • 14
  • 23
1

If your DispatchGroup is a lazy var, try to not call the notify method inside the initialization code block.

lazy var dispatchGroup: DispatchGroup = {
    let dispatchGroup = DispatchGroup()
    
    // not call here dispatchGroup.notify(...

    return dispatchGroup
}()

You need to call all the enter methods before the notify method:

dispatchGroup.enter()

dispatchQueue.async(group: dispatchGroup) {
    // ...
    self.dispatchGroup.leave()
}

dispatchGroup.notify(queue: .main) {
    print("all finished.")
}
pableiros
  • 14,932
  • 12
  • 99
  • 105