Dispatchworkitem Not Notifying Main Thread

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.

How do I notify main thread when workItem is done on background thread?

Use DispatchQueue.main.async just like you would to run any code on the main queue.

let workItem = DispatchWorkItem {
//
// Some tasks
//

DispatchQueue.main.async {
NSProgressIndicator.stopAnimation(self)
}
}

Why does DispatchWorkItem notify crash?

That first example you share (which I gather is directly from the tutorial) is not well written for a couple of reasons:

  1. It's updating a variable from multiple threads. That is an inherently non thread-safe process. It turns out that for reasons not worth outlining here, it's not technically an issue in the author's original example, but it is a very fragile design, illustrated by the non-thread-safe behavior quickly manifested in your subsequent examples.

    One should always synchronize access to a variable if manipulating it from multiple threads. You can use a dedicated serial queue for this, a NSLock, reader-writer pattern, or other patterns. While I'd often use another GCD queue for the synchronization, I think that's going to be confusing when we're focusing on the GCD behavior of DispatchWorkItem on various queues, so in my example below, I'll use NSLock to synchronize access, calling lock() before I try to use value and unlock when I'm done.

  2. You say that first example displays "20". That's a mere accident of timing. If you change it to say ...

    let workItem = DispatchWorkItem {
    os_log("starting")
    Thread.sleep(forTimeInterval: 2)
    value += 5
    os_log("done")
    }

    ... then it will likely say "15", not "20" because you'll see the notify for the workItem.perform() before the async call to the global queue is done. Now, you'd never use sleep in real apps, but I put it in to illustrate the timing issues.

    Bottom line, the notify on a DispatchWorkItem happens when the dispatch work item is first completed and it won't wait for the subsequent invocations of it. This code entails what is called a "race condition" between your notify block and the call you dispatched to that global queue and you're not assured which will run first.

  3. Personally, even setting aside the race conditions and the inherently non thread-safe behavior of mutating some variable from multiple threads, I'd advise against invoking the same DispatchWorkItem multiple times, at least in conjunction with notify on that work item.

  4. If you want to do a notification when everything is done, you should use a DispatchGroup, not a notify on the individual DispatchWorkItem.

Pulling this all together, you get something like:

import os.log

var value = 10
let lock = NSLock() // a lock to synchronize our access to `value`

func notifyExperiment() {
// rather than using `DispatchWorkItem`, a reference type, and invoking it multiple times,
// let's just define some closure or function to run some task

func performTask(message: String) {
os_log("starting %@", message)
Thread.sleep(forTimeInterval: 2) // we wouldn't do this in production app, but lets do it here for pedagogic purposes, slowing it down enough so we can see what's going on
lock.lock()
value += 5
lock.unlock()
os_log("done %@", message)
}

// create a dispatch group to keep track of when these tasks are done

let group = DispatchGroup()

// let's enter the group so that we don't have race condition between dispatching tasks
// to the queues and our notify process

group.enter()

// define what notification will be done when the task is done

group.notify(queue: .main) {
self.lock.lock()
os_log("value = %d", self.value)
self.lock.unlock()
}

// Let's run our task once on the global queue

DispatchQueue.global(qos: .utility).async(group: group) {
performTask(message: "from global queue")
}

// Let's run our task also on a custom queue

let customQueue = DispatchQueue(label: "com.appcoda.delayqueue1", qos: .utility)
customQueue.async(group: group) {
performTask(message: "from custom queue")
}

// Now let's leave the group, resolving our `enter` at the top, allowing the `notify` block
// to run iff (a) all `enter` calls are balanced with `leave` calls; and (b) once the `async(group:)`
// calls are done.

group.leave()
}

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 notify not working

If you're executing asynchronous code on Playground then you need to enable indefinite execution by adding following code on top:

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

I can test your code on Playground and it's working fine.

How to avoid data race with GCD DispatchWorkItem.notify?

EDIT (2019-01-07): As mentioned by @Rob in a comment on the question, this can't be reproduced anymore with recent versions of Xcode/Foundation (I don't have Xcode installed anymore, I won't guess a version number). There is no workaround required.


Well looks like I found out. Using a DispatchGroup.notify to get notified when the group's dispatched items have completed, instead of DispatchWorkItem.notify, avoids the data race. Here's the same-ish snippet without the data race:

  private func incrementAsync() {
let queue = DispatchQueue.global(qos: .background)

let item = DispatchWorkItem { [weak self] in
guard let strongSelf = self else { return }
strongSelf.x += 1
}

let group = DispatchGroup()
group.notify(queue: .main) { [weak self] in
guard let strongSelf = self else { return }
print("> \(strongSelf.x)")
}
queue.async(group: group, execute: item)
}

So DispatchGroup introduces a happens-before relationship and notify is safely called after the threads (in this case, a single async work item) finished execution, while DispatchWorkItem.notify doesn't offer this guarantee.

Wait until DispatchWorkItem is done

To be honest, I don't clearly understand what you wanted, but it's inappropriate to use such a thing as usleep(2000).

So, there is a possible solution, but it's general and you probably need to modify it for your needs.

let group = DispatchGroup()
var info: Info?

override func viewDidLoad() {
super.viewDidLoad()

DispatchQueue.global().asyncAfter(deadline: .now() + 0.35) { [weak self] in
self?.getInfo()
}
}

func getInfo() {
group.enter()
asyncFunc()

group.notify(queue: .main) { [weak self] in
self?.setInfo(self?.info)
}
}

func asyncFunc() {
..... { [weak self] info in
self?.info = info
self?.group.leave()
}
}

If you want to disable user interaction while something is loading, it's better to show progressive loader, but not just freeze the application. In case with the loader, users will understand, that the app isn't frozen.

There is an example of using DispatchWorkItem with DispatchGroup:

let dispatchWorkItem = DispatchWorkItem{
print("work item start")
sleep(1)
print("work item end")
}

let dg = DispatchGroup()
//submiy work items to the group
let dispatchQueue = DispatchQueue(label: "custom dq")
dispatchQueue.async(group: dg) {
print("block start")
sleep(2)
print("block end")
}
DispatchQueue.global().async(group: dg, execute: dispatchWorkItem)
//print message when all blocks in the group finish
dg.notify(queue: DispatchQueue.global()) {
print("dispatch group over")
}

Code from here



Related Topics



Leave a reply



Submit