Waiting until the task finishes

SwiftMultithreadingAsynchronousSwift3Grand Central-Dispatch

Swift Problem Overview


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.

Swift Solutions


Solution 1 - Swift

Use DispatchGroups to achieve this. You can either get notified when the group's enter() and leave() calls are balanced:

func myFunction() {
    var a: Int?

    let group = DispatchGroup()
    group.enter()

    DispatchQueue.main.async {
        a = 1
        group.leave()
    }

    // does not wait. But the code in notify() gets run 
    // after enter() and leave() calls are balanced

    group.notify(queue: .main) {
        print(a)
    }
}

or you can wait:

func myFunction() {
    var a: Int?

    let group = DispatchGroup()
    group.enter()

    // avoid deadlocks by not using .main queue here
    DispatchQueue.global(attributes: .qosDefault).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.

Solution 2 - Swift

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
}

Solution 3 - Swift

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)") 
}

Solution 4 - Swift

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.

Solution 5 - Swift

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!)
        }
    }
}

Solution 6 - Swift

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()
}

Solution 7 - Swift

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBartosz WoźniakView Question on Stackoverflow
Solution 1 - SwiftshallowThoughtView Answer on Stackoverflow
Solution 2 - SwiftUsman JavedView Answer on Stackoverflow
Solution 3 - SwiftSaikrishna RaoView Answer on Stackoverflow
Solution 4 - SwiftSahil AroraView Answer on Stackoverflow
Solution 5 - SwiftAli Ihsan URALView Answer on Stackoverflow
Solution 6 - SwiftvadianView Answer on Stackoverflow
Solution 7 - Swiftuser3064009View Answer on Stackoverflow