Czekam na zakończenie zadania

99

Jak mogę sprawić, by mój kod czekał do zakończenia zadania w DispatchQueue? Czy potrzebuje jakiegoś CompletionHandler czy czegoś takiego?

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

}

Używam Xcode 8.2 i piszę w Swift 3.

Bartosz Woźniak
źródło

Odpowiedzi:

229

DispatchGroupAby to osiągnąć, użyj s. Możesz otrzymywać powiadomienia, gdy grupy enter()i leave()połączenia są zrównoważone:

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

lub możesz poczekać:

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
}

Uwaga : group.wait()blokuje bieżącą kolejkę (prawdopodobnie główną kolejkę w twoim przypadku), więc musisz przejść do dispatch.asyncinnej kolejki (jak w powyższym przykładowym kodzie), aby uniknąć zakleszczenia .

płytka myśl
źródło
Chcę wykonać funkcję w innej klasie, ale chcę poczekać na zakończenie tej funkcji, a następnie w bieżącej klasie kontynuować, jak mogę sobie z tym poradzić?
Saeed Rahmatolahi
2
@SaeedRahmatolahi: Albo użyj waitpodejścia (jeśli blokowanie nie stanowi dla ciebie problemu, tj. Jeśli nie jesteś w głównym wątku), albo zapewnij procedurę obsługi zakończenia lub użyj metody powiadamiania w klasie wywołującej.
shallowThought
Dlaczego dzwonisz group.enterpoza blokiem asynchronicznym? Czy nie powinno być obowiązkiem każdego bloku wchodzenie i opuszczanie grupy?
Bill
4
@Bill waitczeka, aż enteri leavepołączenia są zrównoważone. Jeśli umieścisz enterzamknięcie, waitnie będzie czekał, ponieważ enternie został jeszcze wywołany, a zatem liczba enteri leavepołączenia zrównoważone (# enter == 0, # leav == 0).
shallowThought
1
@rustyMagnet do testów najprawdopodobniej nie jest to właściwa droga. XCTTestExpectationZamiast tego użyj s. Zobacz ten przykładowy kod
shallowThought
26

W Swift 3 nie ma potrzeby obsługi zakończenia, gdy DispatchQueuekończy jedno zadanie. Ponadto możesz osiągnąć swój cel na różne sposoby

Oto jeden sposób:

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

Będzie czekać, aż pętla się zakończy, ale w tym przypadku główny wątek zostanie zablokowany.

Możesz też zrobić to samo:

    let myGroup = DispatchGroup()
    myGroup.enter()
    //// Do your task

    myGroup.leave() //// When your task completes
     myGroup.notify(queue: DispatchQueue.main) {

        ////// do your remaining work
    }

I ostatnia rzecz: jeśli chcesz używać CompleteHandler po zakończeniu zadania przy użyciu DispatchQueue, możesz użyć DispatchWorkItem.

Oto przykład użycia 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
}
Usman Javed
źródło
1
Wypróbowałem je wszystkie i żadna nie działała podczas próby obsługi wywołań
2

Użyj grupy wysyłkowej

   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.
Sahil Arora
źródło
5
Zakładając, że wywołasz to w głównej kolejce, spowoduje to zakleszczenie.
shallowThought
@shallowThought So true.
Prateekro
Chcę wykonać funkcję w innej klasie, ale chcę poczekać na zakończenie tej funkcji, a następnie w bieżącej klasie kontynuować, jak mogę sobie z tym poradzić?
Saeed Rahmatolahi
1
Chociaż powyższy przykład zablokuje główny wątek, jak pokazały poprzednie odpowiedzi, zawijanie wewnątrz DispatchQueue.global().async{}nie zablokuje głównej kolejki.
JonnyB
2

Wersja Swift 5 rozwiązania

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

}

Saikrishna Rao
źródło
1

Szybki 4

W takich sytuacjach można użyć funkcji Async. Kiedy używasz DispatchGroup(), czasami może wystąpić zakleszczenie .

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!)
        }
    }
}
Ali Ihsan URAL
źródło