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
Related
I'm downloading some JSON from an API, but there is another prerequisite (an animation) that must be complete before we move to the next screen.
I've decided to use DispatchGroup to do this, but also need to have a reload as well.
I've come up with a solution that uses a completion handler that is either dispatchGroup.leave or simply moving to the next screen, since we only need to download the data once.
let dispatchGroup = DispatchGroup()
var userName: String?
func fetchData() {
dispatchGroup.enter()
dispatchGroup.enter()
resolveDataRequirements(dispatchGroup.leave)
dispatchGroup.notify(queue: .main) { [weak self] in
self?.moveToNextScreen()
}
}
func resolveDataRequirements(_ completion: #escaping(() -> Void)) {
api.resolve { [weak self] result in
switch result {
case .success(let user):
self?.userName = user
completion()
case .failure:
break
}
}
}
func moveToNextScreen() {
// move to next screen, but passes the user
}
// reload data, and once the data is downloaded move to next screen
func reloadData() {
resolveDataRequirements(moveToNextScreen)
}
// the user might choose to fetch data
fetchData()
// now if the user wanted to reload
reloadData()
Now the problem is that this is in a view model - so the user String is effectively a state that I wish to eradicate.
Is there any way I can pass the user String to dispatchGroup.notify or similar?
You can use inout properties and change userName scope like this:
func fetchData() {
var userName = ""
dispatchGroup.enter()
dispatchGroup.enter()
resolveDataRequirements(dispatchGroup.leave, &userName)
dispatchGroup.notify(queue: .main) { [weak self] in
self?.moveToNextScreen(userName)
}
}
func resolveDataRequirements(_ completion: #escaping(() -> Void), _ userName: inout String) {
api.resolve { [weak self] result in
switch result {
case .success(let user):
userName = user
completion()
case .failure:
break
}
}
}
func moveToNextScreen(_ userName: String) {
print(userName)
}
I just don't understand why you are calling enter() twice and leave() just once
I have a scenario where I want to perform three distinct asynchronous tasks in parallel. Once all three tasks are complete, I then want the calling method to be aware of this and to call its own completion handler.
Below is a very simplified version of the logic for this:
class ViewController: UIViewController {
func doTasks(with object: Object, completionHandler: () -> Void) {
// Once A, B & C are done, then perform a task
wrapupTask()
// When task is complete, call completionHandler
completionHandler()
}
}
fileprivate extension ViewController {
func taskA(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskB(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskC(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
}
I could easily chain the handlers together, but then the task will likely take longer and the code will suck.
Another item I considered was a simple counter that incremented each time a task completed, and then once it hit 3, would then call the wrapupTask() via something like this:
var count: Int {
didSet {
if count == 3 {
wrapupTask()
}
}
}
Another option I have considered is to create an operation queue, and to then load the tasks into it, with a dependency for when to run my wrap up task. Once the queue is empty, it will then call the completion handler. However, this seems like more work than I'd prefer for what I want to accomplish.
My hope is that there is something better that I am just missing.
Just to pick up on what OOPer said, you are looking for DispatchGroup. In the following, the calls to taskA, taskB, and taskC are pseudo-code, but everything else is real:
func doTasks(with object: Object, completionHandler: () -> Void) {
let group = DispatchGroup()
group.enter()
taskA() {
// completion handler
group.leave()
}
group.enter()
taskB() {
// completion handler
group.leave()
}
group.enter()
taskC() {
// completion handler
group.leave()
}
group.notify(queue: DispatchQueue.main) {
// this won't happen until all three tasks have finished their completion handlers
completionHandler()
}
}
Every enter is matched by a leave at the end of the asynchronous completion handler, and only when all the matches have actually executed do we proceed to the notify completion handler.
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
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 ¯\_(ツ)_/¯
After reading Swift 3 evolution on GCD, I am trying to create dispatch group. The problem is the group.notify(queue: do not notify when I pass DispatchQueue.main as a queue, although it does work for background queue.
Also I am not sure my syntax is all correct, as I am trying to convert code from Swift 2 to Swift 3.
typealias CallBack = (result: Bool) -> Void
func longCalculations (completion: CallBack) {
let backgroundQ = DispatchQueue.global(attributes: .qosBackground)
let group = DispatchGroup()
var fill:[Int] = []
for item in 0...200 {
group.enter()
if item > 50 {
fill.append(item)
}
group.leave()
}
//Below in the notify argument If I pass `backgroundQ`, it seems to work correctly but not when DispatchQueue.main is passed.
This code do not work
group.notify(queue: DispatchQueue.main, execute: {
completion(result: true)
})
}
This works correctly
group.notify(queue: backgroundQ, execute: {
completion(result: true)
})
}
_______________________________________________________
longCalculations() { print($0) }
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)}