Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Threading with DispatchGroup

I have the following code, which uses a DispatchGroup to get notified when the tasks are done like this:

func getSomething(completion: ()->()) {
    completion()
}

func doSomeWork(completion: ()->()) {
    let myGroup: DispatchGroup = DispatchGroup()
    for _ in 0..<10 {
        myGroup.enter()
        getSomething {
            myGroup.leave()
        }
    }
    myGroup.notify(queue: DispatchQueue.main) { // this here is the key
        completion()
    }
}

DispatchQueue.global().async {
    print("We are doing background work")
    doSomeWork {
        print("We are done with the work - but on main thread now!!!")
    }
}

So the issue I have is that I call some function on a thread, that function has a completion which is called on some fixed queue.

My options are:

  1. Detect Thread.isMainThread and either notify on a main queue or background queue
  2. Pass the queue we are working on for all function calls Issue: This is not really a smart design pattern
  3. Deal with the fact that this completion has a fixed queue and then manually dispatch again

But I am not happy with any of the options... I'd rather not have so many dispatches.

Imagine this:

DispatchQueue.global().async {
    print("We are doing background work")
    doSomeWork {
        DispatchQueue.global().async {
            print("Now we are in background again")
        }
    }
}

That is already a level-3 closure and is not really nice to work with especially if in that background async call we have another closure or level.

Any help on what to do here would be great! Thank you

like image 323
Janosch Hübner Avatar asked Oct 26 '25 23:10

Janosch Hübner


1 Answers

The issue is that I‘m calling a function bla({ //block code }) on a background queue. bla() calls the completion handler on the main thread though, due to dispatch groups – Janosch Hübner

check again your snippet

func doSomeWork(completion: ()->()) {
    let myGroup: DispatchGroup = DispatchGroup()
    for _ in 0..<10 {
        myGroup.enter()
        getSomething {
            myGroup.leave()
        }
    }
    myGroup.notify(queue: DispatchQueue.main) { // this here is the key
        completion()
    }
}

and see, that because getSomething is synchronous you could simply write

func doSomeWork(completion: ()->()) {
    //let myGroup: DispatchGroup = DispatchGroup()
    for _ in 0..<10 {
        //myGroup.enter()
        getSomething {
            //myGroup.leave()
        }
    }
    //myGroup.notify(queue: DispatchQueue.main) { // this here is the key
        completion()
    //}
}

In case getSomething should be asynchronous, use the proper API to run it within some group

func doSomeWork(completion: ()->()) {
    let myGroup: DispatchGroup = DispatchGroup()
    let queue = DispatchQueue.global()
    for _ in 0..<10 {
        //myGroup.enter()
        queue.async(group: myGroup) {
            getSomething {
            //myGroup.leave()
            }
        }
    }
    myGroup.notify(queue: DispatchQueue.main) { // this here is the key
        completion()
    }
}

Running completion() on the same thread (it is better to say on the same queue) as doSomeWork(completion: ()->()) is simple.

func doSomeWork(completion: ()->()) {
    let myGroup: DispatchGroup = DispatchGroup()
    let queue = DispatchQueue.global()
    for _ in 0..<10 {
        //myGroup.enter()
        queue.async(group: myGroup) {
            getSomething {
            //myGroup.leave()
            }
        }
    }
    //myGroup.notify(queue: DispatchQueue.main) { // this here is the key
        myGroup.wait()
        completion()
    //}
}

check next playground page and see how DispatchQueue.concurrentPerform could change your design and how goup notification works

import PlaygroundSupport
import Dispatch

PlaygroundPage.current.needsIndefiniteExecution = true

let q0 = DispatchQueue.global()
let q1 = DispatchQueue(label: "my_queue", attributes: .concurrent)
let g = DispatchGroup()
let g1 = DispatchGroup()
q0.async(group: g) {
    print("1    message from \(q0): will do some concurrent jobs in the background")
    DispatchQueue.concurrentPerform(iterations: 5, execute: { (i) in
        sleep(1)
        print("\t",i)
    })
    print("2    message from \(q0): all concurrent jobs done")
    q0.async(group: g) {
        print("3    some other long time running on group...")
        sleep(3)
        print("3 ex")
    }
    q0.async(group: g1) {
        print("?    some other long time running on gifferent group...")
        sleep(4)
        print("? ex")
    }
    g1.notify(queue: .main, execute: {
        print("g1 empty")
    })
}
print("4    continue on main")
g.notify(queue: q1) {
    print("5    message from \(q1): finished a")
    DispatchQueue.main.async {
        sleep(1)
        print("6    from main, should stop playground execution?")
        //PlaygroundPage.current.finishExecution()
    }
    print("7    message from \(q1): finished b")

}
g1.notify(queue: .main) {
    print("8    from main, g1 is empty.")

}
print(" ... continue")

which prints on my environment

1    message from <OS_dispatch_queue_global: com.apple.root.default-qos>: will do some concurrent jobs in the background
4    continue on main
 ... continue
8    from main, g1 is empty.
     0
     2
     1
     3
     4
2    message from <OS_dispatch_queue_global: com.apple.root.default-qos>: all concurrent jobs done
3    some other long time running on group...
?    some other long time running on gifferent group...
3 ex
5    message from <OS_dispatch_queue_concurrent: my_queue>: finished a
7    message from <OS_dispatch_queue_concurrent: my_queue>: finished b
6    from main, should stop playground execution?
? ex
g1 empty
like image 98
user3441734 Avatar answered Oct 29 '25 13:10

user3441734



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!