Waiting until the task finishes
Asked Answered
M

7

147

How could I make my code wait until the task in DispatchQueue finishes? Does it need any completion handler 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.

Mclaren answered 27/2, 2017 at 11:9 Comment(0)
C
312

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)")
}
Caravan answered 27/2, 2017 at 11:29 Comment(10)
I want to execute a function in another class but I want to wait to finish that function and then in the current class continue how can I handle that ?Quarto
@SaeedRahmatolahi: Either use the wait approach (if it is no problem for you to block, i.e. if you are not on the main thread) or provide a completion handler or use the notify approach in your calling class.Caravan
Why do you call group.enter outside the async block? Shouldn't it be each block's responsibility to enter and leave the group?Wylma
@Wylma wait waits until enter and leave calls are balanced. If you put enter in the closure, wait would not wait because enter has not been called yet and thus the number of enter and leave calls are balanced (# enter == 0, # leav == 0).Caravan
@Caravan Can I use group.leave inside group.notify, Actually i have a function and it performs UI updation(no network related task) like reloading tableview scrolling to certain row etc. which takes unpredictable time. So inside that function I have created a dispatch group and i call wait first then enter and at last leave. But it results in deadlock. So I think i have to use notify but i am not sure how to do it as i have to put all the code inside the function in notify then how do i call leave.Golfer
@ShivamPokhriyal: Sounds like GCD is the wrong feature for you. You probably want to conform to some delegate. Something like scrollViewDidScroll. If this does not help you, please post a new question describing what you want to archive.Caravan
✅ Great answer. I wanted to write an async Swift Unit Test. The test was torn down before the async Dispatch Group's had finished. The solution was following your example of putting the GCD inside a function. Thanks!Lovellalovelock
@Lovellalovelock for tests this is most probably not the way to go. Use XCTTestExpectations instead. See this sample codeCaravan
is there a way to do a timeout when using notify ?Agretha
@Caravan using wait blocks, while using notify is asynchronous. I had a use case where we cannot block. But it is ok, I already found the solution some time ago.Agretha
C
34

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
}
Calva answered 27/2, 2017 at 11:33 Comment(2)
I tried them all and none worked while trying to handle firebase calls in a for loopSauerkraut
This solution, while visually elegant, doesn't handle the realistic scenarios where code beyond your control gets run asynchronously (i.e. firebase callbacks as pointed out in the first comment). You'll need .wait() to handle more complex situations.Weevily
D
26

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)") 
}
Dermatologist answered 30/5, 2020 at 12:38 Comment(0)
A
5

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()
}
Acoustic answered 2/2, 2022 at 17:48 Comment(0)
G
4

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.
Grueling answered 27/2, 2017 at 11:18 Comment(4)
Assuming you call this on the main queue, this will cause a deadlock.Caravan
@Caravan So true.Told
I want to execute a function in another class but I want to wait to finish that function and then in the current class continue how can I handle that ?Quarto
Although the above example will block on the main thread, as previous answers have shown, wrapping within DispatchQueue.global().async{} will not block the main queue.Williwaw
D
1

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!)
        }
    }
}
Dogbane answered 17/9, 2018 at 13:54 Comment(0)
P
-3

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.

Prescience answered 2/2, 2022 at 17:17 Comment(1)
sleep is the worst choice. Don't do that. Never. It blocks the thread.Acoustic

© 2022 - 2024 — McMap. All rights reserved.