Swift: Deadlock with DispatchGroup - ios

I've got some issue with CoreData concurrency.
I can't do context.perform while a destination thread is blocked with DispatchGroup.
Here is a simple example which shows the issue:
func upload(objects: [NSManagedObject]) {
let group = DispatchGroup()
for object in objects {
group.enter()
upload(object) {
group.leave()
}
}
group.wait() // current thread is blocked here
someAdditionalWorkToDoInSameThread()
}
func upload(object: NSManagedObject, completion: ()->()) {
let context = object.managedObjectContext
performAlamofireRequest(object) {
context.perform {
// can't reach here because the thread is blocked
update(object)
completion()
}
}
}
Please, help me to reimplement this properly. Thanks.

Using notify on dispatch group instead of wait, should resolve your issues.
Calling wait() blocks current thread for completion of previously submitted work.
notify(queue:execute:) will notify the queue that you passed as argument that the group task has been completed.
func upload(objects: [NSManagedObject], completion: ()->()) {
let group = DispatchGroup()
for object in objects {
group.enter()
upload(object) {
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
completion()
}
}

Related

Swift DispatchGroup notify before task finish

I'm using DispatchGroup to perform a task, but group.notify is being called before the task is completed.
My code:
let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")
queueImage.async(group: group) {
sleep(2)
print("image")
}
queueVideo.async(group: group) {
sleep(3)
print("video")
}
group.notify(queue: .main) {
print("all finished.")
}
Logs:
all finish.
image
video
Update: The question above actually runs correctly as is (as rmaddy pointed out!)
I'm saving this wrong answer below in case others get confused about DispatchQueue's async(group:) methods behavior, since Apple's swift doc on it is currently lousy.
The group's enter() needs to be called before each call to async(), and then the group's leave() needs to be called at end of each async() block, but within the block. It's basically like a refcount that when it reaches zero (no enters remaining), then the notify block is called.
let group = DispatchGroup()
let queueImage = DispatchQueue(label: "com.image")
let queueVideo = DispatchQueue(label: "com.video")
group.enter()
queueImage.async(group: group) {
sleep(2)
print("image")
group.leave()
}
group.enter()
queueVideo.async(group: group) {
sleep(3)
print("video")
group.leave()
}
group.notify(queue: .main) {
print("all finished.")
}
Generic answer : (Swift 5)
let yourDispatchGroup = DispatchGroup()
yourDispatchGroup.enter()
task1FunctionCall {
yourDispatchGroup.leave() //task 1 complete
}
yourDispatchGroup.enter()
task2FunctionCall {
yourDispatchGroup.leave() //task 2 complete
}
.. ..
yourDispatchGroup.enter()
tasknFunctionCall {
yourDispatchGroup.leave() //task n complete
}
dispatchGroup.notify(queue: .main) {
//This is invoked when all the tasks in the group is completed.
}
If your DispatchGroup is a lazy var, try to not call the notify method inside the initialization code block.
lazy var dispatchGroup: DispatchGroup = {
let dispatchGroup = DispatchGroup()
// not call here dispatchGroup.notify(...
return dispatchGroup
}()
You need to call all the enter methods before the notify method:
dispatchGroup.enter()
dispatchQueue.async(group: dispatchGroup) {
// ...
self.dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
print("all finished.")
}

Accessing main queue via DispatchGroup vs. DispatchQueue

I'm using DispatchGroup in a class that runs on a background thread. Occasionally, I need to update the UI, so I call the following code:
dispatchGroup.notify(queue: .main) {
self.delegate?.moveTo(sender: self, location: location)
self.delegate?.updateLabel(sender: self, item: self.currentItem)
}
Unfortunately, nothing happens. However, if I call the same code, via DispatchQueue.main.async { }, like so:
DispatchQueue.main.async {
self.delegate?.moveTo(sender: self, location: location)
self.delegate?.updateLabel(sender: self, item: self.currentItem)
}
...the delegate call gets made. I was under the impression dispatchGroup.notify(queue: .main) { } is equivalent to DispatchQueue.main.async { }.
Why are these not the same?
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 to cancel a thread running in the background ? (iOS)

This is my function using dispatch queue, I would like to cancel it when it is running in the background. How can I do that?
extension DispatchQueue {
static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
DispatchQueue.global(qos: .background).async {
background?()
if let completion = completion {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
completion()
})
}
}
}
}
DispatchQueue.background(background: {
do {
}
catch let error {
// Error handling
}
}, completion:{
})
you can make use of DispatchWorkItem with DispatchGroup.
// create a work item with the custom code
let workItem = DispatchWorkItem {
// Insert your code here
}
//Create dispatch group
let dispatchGroup = DispatchGroup()
// execute the workItem with dispatchGroup
DispatchQueue.global().async(group: dispatchGroup, execute: workItem)
//Handle code after the completion of global queue
dispatchGroup.notify(queue: DispatchQueue.global()) {
print("global queue execution completed")
}
//when the App goes to background cancel the workItem
workItem.cancel()
You should definitely check the Apple Official Documentation concerning :
OperationQueue
BlockOperation
But also this WWDC 2014
Hope it will help you and build up your knowledge base about iOS and higher level API.

How to call multiple functions in order one after another as long as the previous is complete

I am trying to call 3 functions in order but each function needs to have been completed before the next should run. Each function has a completion handler that calls another function upon completion. After reading lots online about dispatch queues I though this may be the best way to approach it, that's if I am understanding it correctly of course. When I run my code Each function is called in order but not when the previous has been completed. In the first function I am downloading an image from firebase but the second function gets called before the image has downloaded. I've taken out specifics in my code but this is what I have so far.
typealias COMPLETION = () -> ()
let functionOne_completion = {
print("functionOne COMPLETED")
}
let functionTwo_completion = {
print("functionTwo COMPLETED")
}
let functionThree_completion = {
print("functionThree COMPLETED")
}
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue(label: "com.myApp.myQueue")
queue.sync {
functionOne(completion: functionOne_completion)
functionTwo(completion: functionTwo_completion)
functionThree(completion: functionThree_completion)
}
func functionOne(completion: #escaping COMPLETION) {
print("functionOne STARTED")
completion()
}
func functionTwo(completion: #escaping COMPLETION) {
print("functionTwo STARTED")
completion()
}
func functionThree(completion: #escaping COMPLETION) {
print("functionThree STARTED")
completion()
}
You could use DispatchGroup
DispatchQueue.global().async {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
functionOne { dispatchGroup.leave() }
dispatchGroup.wait() //Add reasonable timeout
dispatchGroup.enter()
functionTwo { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.enter()
functionThree { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.notify(queue: .main) {
//All tasks are completed
}
}
You need to call the second function on the completion of the first.
Something like:
func first(_ completion : #escaping()->()){
print("first")
completion()
}
func second(_ completion : #escaping()->()){
print("second")
}
func third(){
print("third")
}
override func viewDidLoad(){
....
first{
self.second{
self.third()
}
}
}
So when your image download gets finished, inside the completion block where you get the callback of download completion, you should call your second method/block passed as argument which in turn will call your second method.

Dispatch Group not working for completion block

Trying to get return reply objects from a network call. The session is a class that is using the star scream API. I just can't seem to get this to work. It's only printing out 1 set of results which is the from the first id. What am I missing here?
let myGroup = DispatchGroup()
for i in 0 ..< marketIds.count {
myGroup.enter()
self.session.retrieve(withMethod: MarketKeys.key, withParameters: [MarketKeys.id: marketIds[i]], completion: { (results, error) in
print("results \n")
print(results!)
myGroup.leave()
})
}
myGroup.notify(queue:.main) {
print("Done")
}
This article gives you a quick reference guide to simple DispatchGroup use.
An example:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
longRunningFunction { dispatchGroup.leave() }
dispatchGroup.enter()
longRunningFunctionTwo { dispatchGroup.leave() }
dispatchGroup.notify(queue: .main) {
print("Both functions complete 👍")
}
The notify function is called when all items in the queue have been processed and allows you to react to this accordingly. So the example above will run two long running tasks and then will output "Both functions complete 👍"
Add this so notification to group can be sent
myGroup.notify(queue: .main) {
print("Both functions complete 👍")
}

Resources