Grand Central Dispatch-Check for Task Completion - swift

I want to do a lengthy background operation;after completion I need to refresh a TableView
let globalQueue = DispatchQueue.global()
globalQueue.async {
//My lengthy code
}
I need to do this after the Async Task Completes
treeview.reloadData()
How can I hook to GCD Task completion Event? I have C# Background, I'am new to SWIFT.. Please advice.

You just need to place it in a main queue after your code:
let globalQueue = DispatchQueue.global()
globalQueue.async {
// Your code here
DispatchQueue.main.async {
self.treeview.reloadData()
}
}

I would suggest using a DispatchGroup. With a group you can create dependencies and be notified when everything has completed.
// create a group to synchronize our tasks
let group = DispatchGroup()
// The 'enter' method increments the group's task count…
group.enter()
let globalQueue = DispatchQueue.global()
globalQueue.async {
// my lengthy code
group.leave()
}
// closure will be called when the group's task count reaches 0
group.notify(queue: .main) { [weak self] in
self?.tableView.reloadData()
}

Related

Correct way to perform async operations sequentially

I need to perform an async operation for each element in an array, one at at time. This operation calls back on the main queue.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let queue = DispatchQueue(label: "Serial Queue")
queue.sync {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
group.wait()
}
}
print(results) // Never reached
completion()
}
The WebService call isn't calling back - which I think is telling me the main queue is blocked, but I can't understand why.
You should use group.notify() rather than group.wait(), since the latter is a synchronous, blocking operation.
I also don't see a point of dispatching to a queue if you only dispatch a single work item once.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
Maybe it's just a typo but basically don't run the queue synchronously.
Then instead of wait use notify outside(!) of the loop and print the results within the queue.
queue.async {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
I d'ont think your main queue is locked, otherwise you would probably have an infinite loading on your app, as if it crashed ( in MacOS that's for sure ).
Here is what worked for me, maybe it will help :
class func synchronize(completion: #escaping (_ error: Bool) -> Void) {
DispatchQueue.global(qos: .background).async {
// Background Thread
var error = false
let group = DispatchGroup()
synchronizeObject1(group: group){ error = true }
synchronizeObject2(group: group){ error = true }
synchronizeObject3(group: group){ error = true }
group.wait() // will wait for everyone to sync
DispatchQueue.main.async {
// Run UI Updates or call completion block
completion(error)
}
}
}
class func synchronizeObject1(group: DispatchGroup, errorHandler: #escaping () -> Void){
group.enter()
WebservicesController.shared.getAllObjects1() { _ in
// Do My stuff
// Note: if an error occures I call errorHandler()
group.leave()
}
}
If I would say, it may come from the queue.sync instead of queue.async. But I'm not an expert on Asynchronous calls.
Hope it helps

How to avoid data race with GCD DispatchWorkItem.notify?

With Swift 3.1 on XCode 8.3, running the following code with the Thread Sanitizer finds a data race (see the write and read comments in the code):
private func incrementAsync() {
let item = DispatchWorkItem { [weak self] in
guard let strongSelf = self else { return }
strongSelf.x += 1 // <--- the write
// Uncomment following line and there's no race, probably because print introduces a barrier
//print("> DispatchWorkItem done")
}
item.notify(queue: .main) { [weak self] in
guard let strongSelf = self else { return }
print("> \(strongSelf.x)") // <--- the read
}
DispatchQueue.global(qos: .background).async(execute: item)
}
This seems pretty strange to me as the documentation for the DispatchWorkItem mentions that it allows:
getting notified about their completion
which implies that the notify callback is called once the work item's execution is done.
So I would expect that there would be a happens-before relationship between the DispatchWorkItem's work closure and its notify closure. What would be the correct way, if any, to use a DispatchWorkItem with a registered notify callback like this that wouldn't trigger the Thread Sanitizer error?
I tried registering the notify with item.notify(flags: .barrier, queue: .main) ... but the race persisted (probably because the flag only applies to the same queue, documentation is sparse on what the .barrier flag does). But even calling notify on the same (background) queue as the work item's execution, with the flags: .barrier, results in a race.
If you wanna try this out, I published the complete XCode project on github here: https://github.com/mna/TestDispatchNotify
There's a TestDispatchNotify scheme that builds the app without tsan, and TestDispatchNotify+Tsan with the Thread Sanitizer activated.
Thanks,
Martin
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.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
var job = DispatchWorkItem {
for i in 0..<3 {
DispatchQueue.main.async {
print("job", i)
}
}
DispatchQueue.main.async {
print("job done")
}
}
job.notify(queue: .main) {
print("job notify")
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now(), execute: job)
usleep(100)
job.cancel()
if you guess that this snippet prints out
job 0
job 1
job 2
job done
job notify
you are absolutely right!
increase a deadLine ...
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.01, execute: job)
and you've got
job notify
even though the job executes never
notify has nothing with synchronization of any data captured by DispatchWorkItem's closure.
Let try this example with DispatchGroup!
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let group = DispatchGroup()
group.notify(queue: .main) {
print("group notify")
}
And see the result
group notify
!!! WTF !!! Do you still think you solved the race in your code?
To synchronize any read, write ... use the serial queue, barrier, or semaphore. Dispatch group is totally different beast :-) With dispatch groups you can group together multiple tasks and either wait for them to complete or receive a notification once they complete.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let job1 = DispatchWorkItem {
sleep(1)
DispatchQueue.main.async {
print("job 1 done")
}
}
let job2 = DispatchWorkItem {
sleep(2)
DispatchQueue.main.async {
print("job 2 done")
}
}
let group = DispatchGroup()
DispatchQueue.global(qos: .background).async(group: group, execute: job1)
DispatchQueue.global(qos: .background).async(group: group, execute: job2)
print("line1")
group.notify(queue: .main) {
print("group notify")
}
print("line2")
prints
line1
line2
job 1 done
job 2 done
group notify

Call completion block when two other completion blocks have been called

I have a function doEverything that takes a completion block. It calls two other functions, doAlpha and doBeta which both have completion blocks. These two functions should run asynchronously. I want to call doEverything's completion block after both of the other functions have called their completion blocks.
Currently, it looks like this:
func doEverything(completion: #escaping (success) -> ())) {
var alphaSuccess = false
var betaSuccess = false
doAlpha { success in
alphaSuccess = success
}
doBeta { success in
betaSuccess = success
}
// We need to wait here
completion(alphaSuccess && betaSuccess)
}
doAlpha and doBeta should run at the same time and, once they've both completed, the completion block should be called with the result of alpha and beta.
I've read into dispatch groups and barriers but I'm not sure which is the most appropriate, how they both introduce new scope (with regards to the two variables I'm using) and how I should implement that.
Many thanks.
Grand Central Dispatch (GCD) is a pretty good choice of what are you trying to do here, you can achieve this by using DispatchQueue and DispatchGroup, as follows:
Swift 3:
func doEverything(completion: #escaping () -> ()) {
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
let group = DispatchGroup()
group.enter()
queue.async (group: group) {
print("do alpha")
group.leave()
}
group.enter()
queue.async (group: group) {
print("do beta")
group.leave()
}
group.notify(queue: DispatchQueue.main) {
completion()
}
}
Or, you can implement it this way (which I find more readable):
func doEverything(completion: #escaping () -> ()) {
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
let group = DispatchGroup()
queue.async (group: group) {
print("do alpha")
}
queue.async (group: group) {
print("do beta")
}
group.notify(queue: DispatchQueue.main) {
completion()
}
}
Note that I removed the success flag from the completion closure.
At this case, "do beta" (the execution of the second queue.async) won't be executed until "do alpha" (the execution of the first queue.async) finished, and that's because queue target is .main. If you want to let both of queue.async work concurrently, there is no need to create an extra queue, the same queue should does the work, by replacing:
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent, target: .main)
with:
let queue = DispatchQueue(label: "reverseDomain", attributes: .concurrent)
Now, the system will control over how both of queue.async tasks should work concurrently (and obviously, group.notify will be executed after the tow of the tasks finish).
Hope this helped.
Ahmad F's answer is correct but, as my functions with callbacks return immediately (like most do) and the callbacks are executed later, I don't need to create a new queue. Here's the original code with changes to make it work.
func doEverything(completion: #escaping (success) -> ())) {
var alphaSuccess = false
var betaSuccess = false
let group = DispatchGroup()
group.enter()
doAlpha { success in
alphaSuccess = success
group.leave()
}
group.enter()
doBeta { success in
betaSuccess = success
group.leave()
}
group.notify(queue: DispatchQueue.main) {
completion(alphaSuccess && betaSuccess)
}
}
I didn't really want to force the completion call onto the main thread, but hey ¯\_(ツ)_/¯

Swift: Retrieve value from asynchronous call before view appears

I'm using HanekeSwift to retrieve cached data and then set it to labels in a swipeView every time the view appears. My code retrieves the data no problem, but because cache.fetch() is asynchronous, when I call my method to update the view, my labels are set to nil. Is there anyway to tell swift to wait until my cached data is retrieved before loading the view?
See code below:
override func viewWillAppear(animated: Bool) {
updateEntries() // updates entries from cache when view appears
}
func updateEntries() {
guard let accessToken = NSUserDefaults.standardUserDefaults().valueForKey("accessToken") as? String else { return }
guard let cachedEntryKey = String(accessToken) + "food_entries.get" as? String else { return }
cache.fetch(key: cachedEntryKey).onSuccess { data in
...
// if successful, set labels in swipeView to data retrieved from cache
...
dispatch_group_leave(dispatchGroup)
} .onFailure { error in
print(error)
...
// if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
...
dispatch_group_leave(dispatchGroup)
}
}
When I step through the above code, it always displays the view and then steps into the cache block. How do I make viewWillAppear() allow updateEntries() to complete and not return out of it until the cache block is executed? Thanks a ton in advance!
Update 1:
The solution below is working pretty well and my calls are made in the correct sequence (my print statement in the notify block executes after the cache retrieval), but my views only update their labels with non-nil values when the server is called. Maybe I'm lumping the wrong code in the notify group?
override func viewWillAppear(animated: Bool) {
self.addProgressHUD()
updateEntries() // updates entries from cache when view appears
}
func updateEntries() {
guard let accessToken = NSUserDefaults.standardUserDefaults().valueForKey("accessToken") as? String else { return }
guard let cachedEntryKey = String(accessToken) + "food_entries.get" as? String else { return }
let dispatchGroup = dispatch_group_create()
dispatch_group_enter(dispatchGroup)
dispatch_group_async(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
cache.fetch(key: cachedEntryKey).onSuccess { data in
...
// if successful, set labels in swipeView to data retrieved from cache
...
} .onFailure { error in
print(error)
...
// if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
...
}
}
dispatch_group_notify(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
print("Retrieved Data")
self.removeProgressHUD()
}
}
Update 2:
Also, I'm getting this warning in the console when I switch views. I think I'm locking up the main thread with the above code
"This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release."
Note:
enter group before calling asynchronous method
leave group is each of the respective completion/failure handlers
dispatch UI updates in notify block to main queue
Thus:
func updateEntries() {
guard let accessToken = NSUserDefaults.standardUserDefaults().valueForKey("accessToken") as? String else { return }
guard let cachedEntryKey = String(accessToken) + "food_entries.get" as? String else { return }
let group = dispatch_group_create()
dispatch_group_enter(group)
cache.fetch(key: cachedEntryKey).onSuccess { data in
...
// if successful, set labels in swipeView to data retrieved from cache
...
dispatch_group_leave(group)
} .onFailure { error in
print(error)
...
// if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
...
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
print("Retrieved Data")
self.removeProgressHUD()
}
}
Ok suggestions from everyone helped a ton on this. Think I got it. I need to make sure my cache block isn't blocking the main queue. See code below
EDIT
Thanks to #Rob for helping me make the proper adjustments to make this work
let dispatchGroup = dispatch_group_create()
dispatch_group_enter(dispatchGroup)
cache.fetch(key: cachedEntryKey).onSuccess { data in
...
// if successful, set labels in swipeView to data retrieved from cache
...
dispatch_group_leave(dispatchGroup)
} .onFailure { error in
print(error)
...
// if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
...
dispatch_group_leave(dispatchGroup)
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue()) {
print("Retrieved Data")
self.removeProgressHUD()
}
Here's simple example that you can stage a loading screen. I just create a alert view, also you can create your custom loading indicator view instead.
let alert = UIAlertController(title: "", message: "please wait ...", preferredStyle: .alert)
override func viewWillAppear(animated: Bool) {
self.present(alert, animated: true, completion: nil)
updateEntries() // updates entries from cache when view appears
}
func updateEntries() {
guard let accessToken = UserDefaults.standard.value(forKey: "accessToken") as? String,
let cachedEntryKey = (accessToken + "food_entries.get") as? String else {
return
}
cache.fetch(key: cachedEntryKey).onSuccess { data in
...
// update value in your UI
alert.dismiss(animated: true, completion: nil)
...
} .onFailure { error in
print(error)
...
// if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
...
}
}
While I entirely agree with #ozgur about displaying some sort of loading indicator from a UX standpoint, I figured the benefit of learning how to use Grand Central Dispatch (Apple's native solution to asynchronous waiting) might help you in the long-term.
You can use dispatch_groups to wait for a block(s) of code to completely finish running before running a completion handler of some sort.
From Apple's documentation:
A dispatch group is a mechanism for monitoring a set of blocks. Your application can monitor the blocks in the group synchronously or asynchronously depending on your needs. By extension, a group can be useful for synchronizing for code that depends on the completion of other tasks.
[...]
The dispatch group keeps track of how many blocks are outstanding, and GCD retains the group until all its associated blocks complete execution.
Here's an example of dispatch_groups in action:
let dispatchGroup = dispatch_group_create()
dispatch_group_async(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
// Run whatever code you need to in here. It will only move to the final
// dispatch_group_notify block once it reaches the end of the block.
}
dispatch_group_notify(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
// Code in here only runs once all dispatch_group_async blocks associated
// with the dispatchGroup have finished completely.
}
The great part about dispatch_groups are that they allow you to run multiple asynchronous blocks at the same time and wait for all of them to finish before running the final completion handler. In other words, you can associate as many dispatch_group_async blocks with the dispatchGroup as you want.
If you wanted to go for the loading indicator approach (which you should), you can run code to display the loading indicator, then move into a dispatch_group with a completion handler to remove the loading indicator and load data into view once the dispatch_group completes.

How to stop a DispatchWorkItem in GCD?

I am currently playing around with Grand Central Dispatch and discovered a class called DispatchWorkItem. The documentation seems a little incomplete so I am not sure about using it the right way. I created the following snippet and expected something different. I expected that the item will be cancelled after calling cancel on it. But the iteration continues for some reason. Any ideas what I am doing wrong? The code seems fine for me.
#IBAction func testDispatchItems() {
let queue = DispatchQueue.global(attributes:.qosUserInitiated)
let item = DispatchWorkItem { [weak self] in
for i in 0...10000000 {
print(i)
self?.heavyWork()
}
}
queue.async(execute: item)
queue.after(walltime: .now() + 2) {
item.cancel()
}
}
GCD does not perform preemptive cancelations. So, to stop a work item that has already started, you have to test for cancelations yourself. In Swift, cancel the DispatchWorkItem. In Objective-C, call dispatch_block_cancel on the block you created with dispatch_block_create. You can then test to see if was canceled or not with isCancelled in Swift (known as dispatch_block_testcancel in Objective-C).
func testDispatchItems() {
let queue = DispatchQueue.global()
var item: DispatchWorkItem?
// create work item
item = DispatchWorkItem { [weak self] in
for i in 0 ... 10_000_000 {
if item?.isCancelled ?? true { break }
print(i)
self?.heavyWork()
}
item = nil // resolve strong reference cycle of the `DispatchWorkItem`
}
// start it
queue.async(execute: item!)
// after five seconds, stop it if it hasn't already
queue.asyncAfter(deadline: .now() + 5) {
item?.cancel()
item = nil
}
}
Or, in Objective-C:
- (void)testDispatchItem {
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
static dispatch_block_t block = nil; // either static or property
__weak typeof(self) weakSelf = self;
block = dispatch_block_create(0, ^{
for (long i = 0; i < 10000000; i++) {
if (dispatch_block_testcancel(block)) { break; }
NSLog(#"%ld", i);
[weakSelf heavyWork];
}
block = nil;
});
// start it
dispatch_async(queue, block);
// after five seconds, stop it if it hasn't already
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (block) { dispatch_block_cancel(block); }
});
}
There is no asynchronous API where calling a "Cancel" method will cancel a running operation. In every single case, a "Cancel" method will do something so the operation can find out whether it is cancelled, and the operation must check this from time to time and then stop doing more work by itself.
I don't know the API in question, but typically it would be something like
for i in 0...10000000 {
if (self?.cancelled)
break;
print(i)
self?.heavyWork()
}
DispatchWorkItem without DispatchQueue
let workItem = DispatchWorkItem{
//write youre code here
}
workItem.cancel()// For Stop
DispatchWorkItem with DispatchQueue
let workItem = DispatchWorkItem{
//write youre code here
}
DispatchQueue.main.async(execute: workItem)
workItem.cancel()// For Stop
Execute
workItem.perform()// For Execute
workItem.wait()// For Delay Execute