Swift DispatchGroup notify before task finish
Asked Answered
T

3

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
Triliteral answered 20/3, 2018 at 4:2 Comment(6)
Running your code in an Xcode 9.2 playground results in the expected output, not the output shown in your question.Taphole
I tested it in an actual app built with Xcode 9.2 and it also worked just fine there too.Instalment
Make sure you have imported Playground support @Taphole says it does not have any issuesNeilson
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 workingCoitus
Actually .. now it's behaving the opposite: printing is happening but immediately ie the sleep is being ignored. this is strange.Coitus
same thing is working fine now in playground.Muck
I
20

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.")
}
Instalment answered 20/3, 2018 at 4:19 Comment(9)
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.Taphole
@Taphole 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.Instalment
Read the corresponding Objective-C documentation. It has more details.Taphole
I suppose this Apple doc covers it enough to be believable. Thanks rmaddy!Instalment
@Triliteral 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.Taphole
@Taphole 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 advises. Cheers!Instalment
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.Taphole
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 workingCoitus
@Instalment 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 :)Neb
T
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.
}
Tectonics answered 21/9, 2020 at 4:12 Comment(0)
I
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.")
}
Illuminator answered 3/9, 2021 at 18:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.