Warten, bis die Aufgabe beendet ist

99

Wie kann ich meinen Code warten lassen, bis die Aufgabe in DispatchQueue abgeschlossen ist? Benötigt es einen CompletionHandler oder so?

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

}

Ich benutze Xcode 8.2 und schreibe in Swift 3.

Bartosz Woźniak
quelle

Antworten:

228

Verwenden Sie DispatchGroups, um dies zu erreichen. Sie können entweder benachrichtigt werden, wenn die Gruppe enter()und die leave()Anrufe ausgeglichen sind:

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

oder du kannst warten:

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
}

Hinweis : group.wait()Blockiert die aktuelle Warteschlange (in Ihrem Fall wahrscheinlich die Hauptwarteschlange), sodass Sie sich in einer dispatch.asyncanderen Warteschlange befinden müssen (wie im obigen Beispielcode), um einen Deadlock zu vermeiden .

flach gedacht
quelle
Ich möchte eine Funktion in einer anderen Klasse ausführen, aber ich möchte warten, bis diese Funktion beendet ist, und dann in der aktuellen Klasse fortfahren. Wie kann ich damit umgehen?
Saeed Rahmatolahi
2
@SaeedRahmatolahi: Verwenden Sie entweder den waitAnsatz (wenn das Blockieren für Sie kein Problem darstellt, dh wenn Sie sich nicht im Hauptthread befinden) oder stellen Sie einen Abschlusshandler bereit, oder verwenden Sie den Benachrichtigungsansatz in Ihrer aufrufenden Klasse.
flach gedacht
Warum rufst du group.enteraußerhalb des asynchronen Blocks an? Sollte es nicht in der Verantwortung jedes Blocks liegen, die Gruppe zu betreten und zu verlassen?
Bill
4
@ Bill waitwartet bis enterund die leaveAnrufe sind ausgeglichen. Wenn Sie setzen enterin der Schließung, waitwürde nicht warten , weil enternoch nicht und somit die Anzahl der aufgerufen wurde enterund leaveAnrufe werden ausgeglichen (# eingeben == 0, # leav == 0).
flach gedacht
1
@rustyMagnet für Tests ist dies höchstwahrscheinlich nicht der richtige Weg. Verwenden Sie XCTTestExpectationstattdessen s. Siehe diesen Beispielcode
flatThought
26

In Swift 3 ist kein Abschluss-Handler erforderlich, wenn DispatchQueueeine Aufgabe abgeschlossen ist. Darüber hinaus können Sie Ihr Ziel auf verschiedene Weise erreichen

Ein Weg ist folgender:

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

Es wird warten, bis die Schleife beendet ist, aber in diesem Fall wird Ihr Haupt-Thread blockiert.

Sie können das auch so machen:

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

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

        ////// do your remaining work
    }

Eine letzte Sache: Wenn Sie CompletionHandler verwenden möchten, wenn Ihre Aufgabe mit DispatchQueue abgeschlossen ist, können Sie verwenden DispatchWorkItem.

Hier ist ein Beispiel für die Verwendung 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
quelle
1
Ich habe sie alle ausprobiert und keiner hat funktioniert, als ich versucht habe, Firebase-Aufrufe in einer for-Schleife zu verarbeiten
2

Versandgruppe verwenden

   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
quelle
5
Angenommen, Sie rufen dies in der Hauptwarteschlange auf, führt dies zu einem Deadlock.
flach gedacht
@shallowThought So wahr.
Prateekro
Ich möchte eine Funktion in einer anderen Klasse ausführen, aber ich möchte warten, bis diese Funktion beendet ist, und dann in der aktuellen Klasse fortfahren. Wie kann ich damit umgehen?
Saeed Rahmatolahi
1
Obwohl das obige Beispiel den Hauptthread blockiert, wie frühere Antworten gezeigt haben, DispatchQueue.global().async{}wird die Hauptwarteschlange durch das Umschließen nicht blockiert.
JonnyB
2

Swift 5-Version der Lösung

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
quelle
1

Swift 4

Sie können die Async-Funktion für diese Situationen verwenden. Bei der Verwendung DispatchGroup()kann es manchmal zu einem Deadlock kommen.

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
quelle