Wait for DispatchQueue [duplicate] - ios

How could I make my code wait until the task in DispatchQueue finishes? Does it need any CompletionHandler or something?
func myFunction() {
var a: Int?
DispatchQueue.main.async {
var b: Int = 3
a = b
}
// wait until the task finishes, then print
print(a) // - this will contain nil, of course, because it
// will execute before the code above
}
I'm using Xcode 8.2 and writing in Swift 3.

If you need to hide the asynchronous nature of myFunction from the caller, use DispatchGroups to achieve this. Otherwise, use a completion block. Find samples for both below.
DispatchGroup Sample
You can either get notified when the group's enter() and leave() calls are balanced:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
a = 1
group.leave()
}
// does not wait. But the code in notify() is executed
// after enter() and leave() calls are balanced
group.notify(queue: .main) {
print(a)
}
}
or you can wait:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
// avoid deadlocks by not using .main queue here
DispatchQueue.global(qos: .default).async {
a = 1
group.leave()
}
// wait ...
group.wait()
print(a) // you could also `return a` here
}
Note: group.wait() blocks the current queue (probably the main queue in your case), so you have to dispatch.async on another queue (like in the above sample code) to avoid a deadlock.
Completion Block Sample
func myFunction(completion: #escaping (Int)->()) {
var a = 0
DispatchQueue.main.async {
let b: Int = 1
a = b
completion(a) // call completion after you have the result
}
}
// on caller side:
myFunction { result in
print("result: \(result)")
}

In Swift 3, there is no need for completion handler when DispatchQueue finishes one task.
Furthermore you can achieve your goal in different ways
One way is this:
var a: Int?
let queue = DispatchQueue(label: "com.app.queue")
queue.sync {
for i in 0..<10 {
print("Ⓜ️" , i)
a = i
}
}
print("After Queue \(a)")
It will wait until the loop finishes but in this case your main thread will block.
You can also do the same thing like this:
let myGroup = DispatchGroup()
myGroup.enter()
//// Do your task
myGroup.leave() //// When your task completes
myGroup.notify(queue: DispatchQueue.main) {
////// do your remaining work
}
One last thing: If you want to use completionHandler when your task completes using DispatchQueue, you can use DispatchWorkItem.
Here is an example how to use DispatchWorkItem:
let workItem = DispatchWorkItem {
// Do something
}
let queue = DispatchQueue.global()
queue.async {
workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
// Here you can notify you Main thread
}

Swift 5 version of the solution
func myCriticalFunction() {
var value1: String?
var value2: String?
let group = DispatchGroup()
group.enter()
//async operation 1
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value1 = //out of async task
group.leave()
}
group.enter()
//async operation 2
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value2 = //out of async task
group.leave()
}
group.wait()
print("Value1 \(value1) , Value2 \(value2)")
}

Use dispatch group
dispatchGroup.enter()
FirstOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.enter()
SecondOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.wait() // Waits here on this thread until the two operations complete executing.

In Swift 5.5+ you can take advantage of Swift Concurrency which allows to return a value from a closure dispatched to the main thread
func myFunction() async {
var a : Int?
a = await MainActor.run {
let b = 3
return b
}
print(a)
}
Task {
await myFunction()
}

Swift 4
You can use Async Function for these situations. When you use DispatchGroup(),Sometimes deadlock may be occures.
var a: Int?
#objc func myFunction(completion:#escaping (Bool) -> () ) {
DispatchQueue.main.async {
let b: Int = 3
a = b
completion(true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
myFunction { (status) in
if status {
print(self.a!)
}
}
}

Somehow the dispatchGroup enter() and leave() commands above didn't work for my case.
Using sleep(5) in a while loop on the background thread worked for me though. Leaving here in case it helps someone else and it didn't interfere with my other threads.

Related

How to execute code *after* async function has finished and returned to caller's context?

I have an async function and I want to execute some task after the function has returned to the original context.
Here's a sample:
actor MyActor {
func doTask() await -> Int {
let result = await operation()
Task {
print("2. All tasks done!")
}
return result
}
}
let actor = MyActor()
let taskResult = await actor.doTask()
print("1. Task done")
I want output always to be
Task done
All tasks done!
However, sometimes the output is reversed.
I suppose this is because Swift might be switching threads when returning to the original caller.
Is there any way to actually wait for it so that "2. All tasks done" is called last?
I want to execute some task after the function has returned to the original context.
There's no magic here. If you want a task to execute at a certain point, then you need to start it at that point, not before.
What you are asking for is almost literally the same as
How do I make the print("2 ...") appear after the print("1 ...") in the following.
struct MyStruct {
func doTask() -> Int {
let result = operation()
print("2. All tasks done!")
return result
}
}
let actor = MyStruct()
let taskResult = actor.doTask()
print("1. Task done")
In your case, the only way to ensure the print("2 ...") appears after the print("1 ...") is to start the asynchronous task after you have done the print("1 ...").
You'll need to split the actor function in two.
actor MyActor {
func doOperation() async -> Int {
let result = await operation()
return result
}
func doTask(_ result: Int) async {
Task {
print("2. All tasks done!")
// Use result in some way if you need to
}
}
}
let actor = MyActor()
let taskResult = await actor.doOperation()
print("1. Task done")
await actor.doTask(taskResult)

Async tasks execution with dependency

Situation:
I have 2 tasks says T1 & T2 in async background mode. T2 depends on T1 and have successBlock which is executes after the completion of the both tasks T1 & T2.
Quick diagram is below for better understanding.
Edit:
To better understanding the tasks, you can assume T1 and T2 are the API calls which always be going to execute in async mode. I need some output data from T1 to hit T2 API. After the completion of the both tasks I need to update UI.
To accomplish this scenario, I have added my first async work in T1 and second work in T2 and dependency of T2 to T1 and successblock have dependency on both tasks.
Code Work
My Tasks
class TaskManager {
static let shared = TaskManager()
func task1Call(complete: #escaping ()->()) {
DispatchQueue.global(qos: .background).async {
for i in 0...10 {
print("~~> Task 1 Executing ..", i)
sleep(1)
}
complete()
}
}
func task2Call(complete: #escaping ()->()) {
DispatchQueue.global(qos: .background).async {
for i in 0...10 {
print("==> Task 2 Executing ..", i)
sleep(1)
}
complete()
}
}
}
Execute Tasks
class Execution {
// Managing tasks with OperationQueue
func executeTaskWithOperation() {
let t1 = BlockOperation {
TaskManager.shared.task1Call {
print("Task 1 Completed")
}
}
let t2 = BlockOperation {
TaskManager.shared.task2Call {
print("Task 2 Completed")
}
}
let successBlock = BlockOperation {
print("Tasks Completed")
}
let oper = OperationQueue()
t2.addDependency(t1)
successBlock.addDependency(t2)
successBlock.addDependency(t1)
oper.addOperations([t1, t2, successBlock], waitUntilFinished: true)
}
}
let e = Execution()
e.executeTaskWithOperation()
Issue:
Both tasks are executing parallelly and successBlock executes before the completion of task 1 and task 2.
Console Output:
==> Task 2 Executing .. 0
Tasks Completed
~~> Task 1 Executing .. 0
~~> Task 1 Executing .. 1
==> Task 2 Executing .. 1
==> Task 2 Executing .. 2
~~> Task 1 Executing .. 2
==> Task 2 Executing .. 3
~~> Task 1 Executing .. 3
==> Task 2 Executing .. 4
~~> Task 1 Executing .. 4
==> Task 2 Executing .. 5
~~> Task 1 Executing .. 5
==> Task 2 Executing .. 6
~~> Task 1 Executing .. 6
==> Task 2 Executing .. 7
~~> Task 1 Executing .. 7
==> Task 2 Executing .. 8
~~> Task 1 Executing .. 8
==> Task 2 Executing .. 9
~~> Task 1 Executing .. 9
~~> Task 1 Executing .. 10
==> Task 2 Executing .. 10
Task 1 Completed
Task 2 Completed
I unable to figure out what wrong I am doing, even same code work fines when I use sync mode instead of async.
Your t1 and t2 are block operations that spawn background threads (which each do some printing and then exit, but it doesn't matter). Once they finish spawning, they're considered completed. successBlock depends on the two background threads being spawned, and then it's done. You want the work in the BlockOperation itself:
class Execution {
// Managing tasks with OperationQueue
func executeTaskWithOperation() {
let t1 = BlockOperation {
for i in 0...10 {
print("~~> Task 1 Executing ..", i)
sleep(1)
}
print("Task 1 completed")
}
let t2 = BlockOperation {
for i in 0...10 {
print("==> Task 2 Executing ..", i)
sleep(1)
}
print("Task 2 Completed")
}
let successBlock = BlockOperation {
print("Tasks Completed")
}
let oper = OperationQueue()
t2.addDependency(t1) // Remove this to see concurrent exec of t1 and t2
successBlock.addDependency(t2)
successBlock.addDependency(t1)
oper.addOperations([t1, t2, successBlock], waitUntilFinished: true)
}
}
let e = Execution()
e.executeTaskWithOperation()
Edit: For execution on a background thread, override Operation.
class AsyncOp: Operation {
let task: String
var running = false
var done = false
init(_ task: String) {
self.task = task
}
override var isAsynchronous: Bool { true }
override var isExecuting: Bool {
get { running }
set {
willChangeValue(forKey: "isExecuting")
running = newValue
didChangeValue(forKey: "isExecuting")
}
}
override var isFinished: Bool {
get { done }
set {
willChangeValue(forKey: "isFinished")
done = newValue
didChangeValue(forKey: "isFinished")
}
}
override func main() {
DispatchQueue.global(qos: .background).async {
self.isExecuting = true
for i in 0...10 {
print("\(self.task) Executing ..", i)
sleep(1)
}
print("Done")
self.isExecuting = false
self.isFinished = true
}
}
override func start() {
print("\(task) starting")
main()
}
}
class Execution {
// Managing tasks with OperationQueue
func executeTaskWithOperation() {
let t1 = AsyncOp("task1")
let t2 = AsyncOp("task2")
let successBlock = BlockOperation {
print("Tasks Completed")
}
let oper = OperationQueue()
t2.addDependency(t1)
successBlock.addDependency(t2)
successBlock.addDependency(t1)
oper.addOperations([t1, t2, successBlock], waitUntilFinished: true)
}
}
let e = Execution()
e.executeTaskWithOperation()
After Joshua's comment , I able to conclude the answer.
Execution changed from OperationQueue to DispatchGroup and DispatchSemaphore.
DispatchGroup : It makes sure both task tasks are done and then it calls notify block.
DispatchSemaphore : It holds the async resource with wait command until we wont send the signal command i.e. we are saying to semaphore to hold yourself until the task1 is not completed.
Sample code of tasks.
class Execution {
// Managing tasks with DispatchGroup
func executeTaskWithGroup() {
let groups = DispatchGroup()
let semaphore = DispatchSemaphore(value: 1)
groups.enter()
semaphore.wait()
TaskManager.shared.task1Call {
groups.leave()
semaphore.signal()
}
groups.enter()
TaskManager.shared.task2Call {
groups.leave()
}
groups.notify(queue: DispatchQueue.global(qos: .background)) {
print("Tasks Completed")
}
}
}
To execute command all we need to do is.
let e = Execution()
e.executeTaskWithGroup()
But above code is executed in the main thread and block the UI. To prevent this you need to call above piece of code in background queue like below.
let queue = DispatchQueue.init(label: "MyQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
queue.async {
let e = Execution()
e.executeTaskWithGroup()
}
Now everything works fine as per my needed.
AddOn
In case, if someone requirement is to call multiple API along with the above scenario then add your tasks in async in the queue.
let queue = DispatchQueue.init(label: "MyQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
queue.async {
let e1 = Execution()
e1.executeTaskWithGroup()
}
queue.async {
let e2 = Execution()
e2.executeTaskWithGroup()
}
Now both e1 and e2 executes parallelly without blocking main thread.
References :
Using Dispatch Group & Semaphore to Group iOS Async Tasks
A Quick Look at Semaphores in Swift
Dexecutor for the rescue here
Disclaimer: I am the owner of Dexecutor
Dexecutor can be used easily for workflow like use case
Here is sample Application

Firebase & Swift: Asynchronous calls, Completion Handlers

I have read up a lot on this subject but have still been stumped on this specific problem. I have many Firebase calls that rely on each other. This is a kind of simplified example of my code. I had trouble making it any shorter and still getting the point across:
class ScoreUpdater {
static let ref = Database.database().reference()
var userAranking = Int?
var userBranking = Int?
var rankingAreceived = false
var rankingBreceived = false
var sum = 0
// Pass in the current user and the current meme
static func beginUpdate(memeID: String, userID: String) {
// Iterate through each user who has ranked the meme before
ScoreUpdater.ref.child("memes/\(memeID)/rankings")observeSingleEvent(of: .value) {
let enumerator = snapshot.children
while let nextUser = enumerator.nextObject() as? DataSnapshot {
// Create a currentUpdater instance for the current user paired with each other user
let currentUpdater = ScoreUpdater()
This is where the asynchronous calls start. Multiple gatherRankingValues functions can run at one time. This function contains a Firebase call which is asynchronous, which is okay for this function. The updateScores however cannot run until gatherRankingValues is finished. That is why I have the completion handler. I think this area is okay based on my debug printing.
// After gatherRankingValues is finished running,
// then updateScores can run
currentUpdater.gatherRankingValues(userA: userID, userB: nextUser.key as! String) {
currentUpdater, userA, userB in
currentUpdater.updateScores(userA: userA, userB:userB)
}
}
}
}
func gatherRankingValues(userA: String, userB: String, completion: #escaping (_ currentUpdater: SimilarityScoreUpdater, _ userA: String, _ userB: String) -> Void) {
// Iterate through every meme in the database
ScoreUpdater.ref.child("memes").observeSingleEvent(of: .value) {
snapshot in
let enumerator = snapshot.children
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
Here is where the main problem comes in. The self.getRankingA and self.getRankingB never run. Both of these methods need to run before the calculation method. I try to put in the "while rankingReceived == false" loop to keep the calculation from starting. I use the completion handler to notify within the self.rankingAreceived and self.rankingBreceived when the values have been received from the database. Instead, the calculation never happens and the loop becomes infinite.
If I remove the while loop waiting for the rankings to be received, the calculations will be "carried out" except the end result ends up being nil because the getRankingA and getRankingB methods still do not get called.
self.getRankingA(userA: userA, memeID: nextMeme.key) {
self.rankingAreceived = true
}
self.getRankingB(userB: userB, memeID: nextMeme.key) {
self.rankingBreceived = true
}
while self.rankingAreceived == false || self.rankingBreceived == false {
continue
}
self.calculation()
}
So yes, every meme gets looped through before the completion is called, but the rankings don't get called. I can't figure out how to get the loop to wait for the rankings from getRankingA and getRankingB and for the calculation method to run before continuing on to the next meme. I need completion of gatherRankingValues (see below) to be called after the loop has been through all the memes, but each ranking and calculation to complete also before the loop gets called again ... How can I within the getRankingA and getRankingB completion handlers tell the meme iterating loop to wait up?
// After every meme has been looped through for this pair of users, call completion
completion(self, userA, userB)
}
}
function getRankingA(userA: String, memeID: String, completion: #escaping () -> Void) {
ScoreUpdater.ref.child("memes/\(memeID)\rankings\(userA)").observeSingleEvent(of: .value) {
snapshot in
self.userAranking = snapshot.value
completion()
}
}
function getRankingB(userB: String, memeID: String, completion: #escaping () -> Void) {
ScoreUpdater.ref.child("memes/\(memeID)\rankings\(userB)").observeSingleEvent(of: .value) {
snapshot in
self.userBranking = snapshot.value
completion()
}
}
func calculation() {
self.sum = self.userAranking + self.userBranking
self.userAranking = nil
self.userBranking = nil
}
func updateScores() {
ScoreUpdater.ref.child(...)...setValue(self.sum)
}
}
Tomte's answer solved one of my problems (thank you!). The calculations will be carried out after userAranking and userBranking are received with this code:
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
let group = DispatchGroup()
group.enter()
self.getRankingA(userA: userA, memeID: nextMeme.key) {
self.rankingAreceived = true
group.leave()
}
group.enter()
self.getRankingB(userB: userB, memeID: nextMeme.key) {
self.rankingBreceived = true
group.leave()
}
// is called when the last task left the group
group.notify(queue: .main) {
self.calculation()
}
}
Still, the completion call to updateScores would happen at the end of the loop but before all of the userArankings and userBrankings are received and before the rankings undergo calculations. I solved this problem by adding another dispatch group:
let downloadGroup = DispatchGroup()
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
let calculationGroup = DispatchGroup()
downloadGroup.enter()
calculationGroup.enter()
self.getRankingA(userA: userA, memeID: nextMeme.key) {
downloadGroup.leave()
calculationGroup.leave()
}
downloadGroup.enter()
calculationGroup.enter()
self.getRankingB(userB: userB, memeID: nextMeme.key) {
downloadGroup.leave()
calculationGroup.leave()
}
// is called when the last task left the group
downloadGroup.enter()
calculationGroup.notify(queue: .main) {
self.calculation() {
downloadGroup.leave()
}
}
}
downloadGroup.notify(queue: .main) {
completion(self, userA, userB)
}
I had to add a completion handler to the calculation method as well to ensure that the updateScores method would be called once userAranking and userBranking undergo calculations, not just once they are received from the database.
Yay for dispatch groups!
To wait for a loop to complete or better, do stuff after some async call is executed, you could use DispatchGroups. This example shows how they work:
let group = DispatchGroup()
var isExecutedOne = false
var isExecutedTwo = false
group.enter()
myAsyncCallOne() {
isExecutedOne = true
group.leave()
}
group.enter()
myAsyncCallTwo() {
isExecutedOTwo = true
group.leave()
}
group.notify(queue: .main) {
if isExecutedOne && isExecutedTwo {
print("hooray!")
} else {
print("nope...")
}
}
UPDATE
This example shows you how the group is used to control the output. There is no need to wait() or something. You just enter the group in every iteration of the loop, leave it in the async callbacks and when every task left the group, group.notify()is called and you can do the calculations:
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
let group = DispatchGroup()
group.enter()
self.getRankingA(userA: userA, memeID: nextMeme.key) {
self.rankingAreceived = true
group.leave()
}
group.enter()
self.getRankingB(userB: userB, memeID: nextMeme.key) {
self.rankingBreceived = true
group.leave()
}
// is called when the last task left the group
group.notify(queue: .main) {
self.calculation()
}
}
group.notify()is called when all the calls have left the group. You can have nested groups too.
Happy Coding!

NSOperationQueue finishes all tasks swift 3

I am trying to achieve NSOperationQueue finishing all tasks operation in swift 3. I create a below demo code and it is working according to my expectation.
func downloadTopStroiesDetails(){
let operationQueue: OperationQueue = OperationQueue()
let operation1 = BlockOperation() {
print("BlockOperation1")
for id in 0...5{
operationQueue.addOperation(downloadArticle(index: id))
}
let operation2 = BlockOperation() {
print("BlockOperation2")
}
operationQueue.addOperation(operation2)
}
operationQueue.addOperation(operation1)
}
func downloadArticle(index:Int) -> Operation {
let operation: Operation = BlockOperation { () -> Void in
print(index)
}
return operation
}
downloadTopStroiesDetails() // start calling
Output :
BlockOperation1
0
1
2
3
4
5
BlockOperation2
But when I call a Web API with Alamofire in downloadArticle method output is different.
func downloadArticle(index:Int) -> Operation {
let operation = BlockOperation(block: {
RequestManager.networkManager.fetchFromNetworkwithID(articleid: index) { (response:Any ,sucess:Bool) in
if sucess{
print(index)
//let art = article.init(json:(response as? json)!)!
// self.saveDataIntoCoreData(data: art)
//self.all_TopArticle.append(art)
}
};
})
return operation
}
Now output :
BlockOperation1
BlockOperation2
0
1
2
3
4
5
What i am doing wrong here ?
Your downloadArticle method is creating a block operation that completes immediately because it in turn performs an asynchronous operation.
You need to prevent the block from reaching the end until the async fetch completes. Using a semaphore would be one solution.
func downloadArticle(index:Int) -> Operation {
let operation = BlockOperation(block: {
let semaphore = DispatchSemaphore(value: 0)
RequestManager.networkManager.fetchFromNetworkwithID(articleid: index) { (response:Any ,sucess:Bool) in
if sucess{
print(index)
//let art = article.init(json:(response as? json)!)!
// self.saveDataIntoCoreData(data: art)
//self.all_TopArticle.append(art)
}
semaphore.signal()
};
semaphore.wait()
})
return operation
}
The use of this semaphore ensure the operation doesn't actually complete until the network fetch is also complete.
You might also want to make your operation queue serial instead of concurrent to ensure you only allow one operation to run at a time. If this is what you want, then set the operation queue's maxConcurrentOperationCount to 1.

How to wait for a closure completion before returning a value

How to wait to return a value after a closure completion.
Example:
func testmethod() -> String {
var abc = ""
/* some asynchronous service call block that sets abc to some other value */ {
abc = "xyz"
}
return abc
}
Now I want the method to return only after xyz value has been set for variable and not empty string.
How to achieve this?
It is possible (However make sure this is what you really want.).
You have to use something that will block a thread until a resource is available, like semaphores.
var foo: String {
let semaphore = DispatchSemaphore(value: 0)
var string = ""
getSomethingAsynchronously { something in
string = something
semaphore.signal()
}
semaphore.wait()
return string
}
Bare in mind that the thread you are working on will be blocked until the getSomethingAsynchronously is done.
Yes, it is possible to wait for the closure function to populate the data and then return the variable. Although, it is suggested to avoid using semaphore to do that, I think that is the only way.
func foo() -> String {
var str = ""
let semaphore = DispathSemaphore(value: 1) //1 if you are waiting for one closure function to signal the semaphore to continue the main thread execution
getAsync() {
//populate the variable
str = "bar"
semaphore.signal()
}
semaphore.wait()
return str
}
Since Swift 5.5 the recommended way is async/await. If the API doesn't provide an async version you can create your own with a Continuation
func testMethod() async -> String {
return await withCheckedContinuation({ continuation in
someAsynchronousServiceCallBlock() { result in
continuation.resume(returning: result)
}
})
}
You have to call the mathod in an asynchronous context for example in a Task
Task {
let result = await testMethod()
}
this is absolutely not possible, because it is just not how asynchronous tasks are working.
what you could do is something like this:
func testmethod(callback: (abc: String) -> Void) {
asyncTask() {
callback(abc: "xyz")
}
}
Have a nice day.
EDIT (for newer Swift Versions):
func testMethod(callback: #escaping (_ parameter: String) -> Void) {
DispatchQueue.global().async { // representative for any async task
callback("Test")
}
}

Resources