Dispatch Group - Cannot Notify to Main Thread

Dispatch group - cannot notify to main thread

After reading post suggested by Matt, I found that I was submitting task to main queue and when I asked to be notified on main thread itself, it got in the deadlock.

I have altered the code and now it is working as intended,

typealias CallBack = (result: [Int]) -> Void
func longCalculations (completion: CallBack) {
let backgroundQ = DispatchQueue.global(attributes: .qosDefault)
let group = DispatchGroup()

var fill:[Int] = []
for number in 0..<100 {
group.enter()
backgroundQ.async(group: group, execute: {
if number > 50 {
fill.append(number)
}
group.leave()

})
}

group.notify(queue: DispatchQueue.main, execute: {
print("All Done"); completion(result: fill)
})
}

longCalculations(){print($0)}

Dispatch Group not notifying?

It's a good practice to make leave top of callback

guard error == nil else { print("get heart rate error");  dispatchGroup.leave() return ; }

guard let unwrappedResults = results as? [HKQuantitySample] else { print("get heart rate error"); dispatchGroup.leave(); return}

let heartRatesAsDouble = unwrappedResults.map {$0.quantity.doubleValue(for: heartRateUnit)}

guard let max = heartRatesAsDouble.max() else { dispatchGroup.leave(); return }

let maxAsCustomHistoricalSample = CustomHistoricalSample(value: max, date: workout.startDate)

heartRateMaxArrayAsCustomHistoricalSample.append(maxAsCustomHistoricalSample)

let average = heartRatesAsDouble.average

let averageAsCustomHistoricalSample = CustomHistoricalSample(value: average, date: workout.startDate)

dispatchGroup.leave()

heartRateAvgArrayAsCustomHistoricalSample.append(averageAsCustomHistoricalSample)

DispatchWorkItem not notifying main thread

If you are running asynchronous code in a playground then you need to enable indefinite execution, otherwise execution may end before the callbacks execute.

Add the following lines to your code in the playground:

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

Once you do this, you will see that the notify executes correctly on the main queue.

Dispatch Groups not working as expected

If there are multiple matches (matches.count > 1) in getExpandedURLsFromText then the result handler is called multiple times, making your dispatch group logic break up.

Clean up your logic.

You have to make sure that the completion handler is always called exactly once when successful and exactly once for each error. You are missing calls to completion handler in multiple places in your guard statements.

Bad dispatch group Notify placement?

In your code the notify is inside the loop. It should be after the for-in body.

The code works fine:

  • if you have all synchronous calls - notify calls immediately - it's okay, your processing has ended
  • if you have some asynchronous calls - notify waits till the end of the last one to finish, as you do enter per loop item

Accessing main queue via DispatchGroup vs. DispatchQueue

Is your dispatchGroup empty (i.e. have no blocks running) at the time when you call notify(queue:)? If not, as documentation states dispatchGroup.notify(queue:)

Schedules a work item to be submitted to a queue when a group of previously submitted block objects have completed.

Which means that your closure will be executed only after the last leave() call, when the group becomes empty. And, of course, enter()s and leave()s must be balanced.

Consider the following example:

let group = DispatchGroup()

group.enter()
someLongRunningTask() {
// completion callback
group.leave()
}

group.enter()
anotherLongRunningTask() {
// completion callback
group.leave()
}

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

In this example all set will be printed only after two callbacks with group.leave() execute.

On the other hand, DispatchQueue.main.async() immediately submits the block to the target queue but it will not necessarily start right after that – there could be running, for example, async block with the .barrier flag.

Update: Implementation of the example above using DispatchQueue (hopefully, it makes things clear):

let group = DispatchGroup()

group.enter()
someLongRunningTask() {
// completion callback
group.leave()
}

group.enter()
anotherLongRunningTask() {
// completion callback
group.leave()
}

group.wait() // waits synchronously for the submitted work to complete
DispatchQueue.main.async {
print("all set")
}

How do I use DispatchGroup in background thread?

Given what you've described, I probably wouldn't use a dispatch group at all here. I'd just chain the methods:

@objc func doWorkFunctions() {
DispatchQueue.global(qos: .background).async {
self.firstFunction {
self.secondFunction {
DispatchQueue.main.async {
print("All tasks completed")
}
}
}
}

But assuming you have a good reason for a group here, what you need to do is to use .notify to synchronize them. .notify says "when the group is empty, submit this block to this queue."

@objc func doWorkFunctions(){
let queue = DispatchQueue.global(qos: .background)

taskGroup.enter()
queue.async {
self.firstFunction {
self.taskGroup.leave()
}
}

taskGroup.notify(queue: queue) {
self.taskGroup.enter()

self.secondFunction {
self.taskGroup.leave()
}

self.taskGroup.notify(queue: .main) {
print("All tasks completed")
}
}
}

(You probably don't need taskGroup to be an instance property here. You could make it a local variable and have fewer self. references required. Each block has a reference to the group, so it will live until all the blocks have completed.)



Related Topics



Leave a reply



Submit