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:
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 ofDispatchWorkItem
on various queues, so in my example below, I'll useNSLock
to synchronize access, callinglock()
before I try to usevalue
andunlock
when I'm done.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 theworkItem.perform()
before theasync
call to the global queue is done. Now, you'd never usesleep
in real apps, but I put it in to illustrate the timing issues.Bottom line, the
notify
on aDispatchWorkItem
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 yournotify
block and the call you dispatched to that global queue and you're not assured which will run first.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 withnotify
on that work item.If you want to do a notification when everything is done, you should use a
DispatchGroup
, not anotify
on the individualDispatchWorkItem
.
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
Compare Value from Two Struct in Swift
Swift 3 Errors with Additional Data
What Are 3 Items in a Swift Method Parameter For
Ambiguous Method Overload with Closures in Swift, But Only When Closure Returns a Value
How to Notify a Queue in Swift (Gcd)
Swift Uikit Dynamics Add Collision Boundary. After Rotation Does Not Work Correctly
Need Clarification for Swift Type Properties
Show Datepicker Hourandminute on Print Statement Swiftui
Make Swiftui Rectangle Same Height or Width as Another Rectangle
A Method Without Parameters Is Calling for an Argument
How to Decode Utf8-Literals Like "\Xc3\Xa6" in Swift 5
Get Images from Document Directory Not File Path Swift 3
Initializer for Conditional Binding Must Have Optional Type, Not '[String:Any]'
How to Pass Closure as a Parameter in Perform(Selector, Withobject)
How to Procedurally Draw Rectangle/Lines in Swift Using Cgcontext