Esperando hasta que termine la tarea

99

¿Cómo puedo hacer que mi código espere hasta que finalice la tarea en DispatchQueue? ¿Necesita CompletionHandler o algo así?

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

}

Estoy usando Xcode 8.2 y escribiendo en Swift 3.

Bartosz Woźniak
fuente

Respuestas:

229

Utilice DispatchGroups para lograr esto. Puede recibir una notificación cuando el grupo enter()y las leave()llamadas estén equilibradas:

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

o puedes esperar:

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
}

Nota : group.wait()bloquea la cola actual (probablemente la cola principal en su caso), por lo que debe hacerlo dispatch.asyncen otra cola (como en el código de muestra anterior) para evitar un punto muerto .

pensamiento superficial
fuente
Quiero ejecutar una función en otra clase pero quiero esperar para terminar esa función y luego en la clase actual continuar, ¿cómo puedo manejar eso?
Saeed Rahmatolahi
2
@SaeedRahmatolahi: O use el waitenfoque (si no es un problema para usted bloquear, es decir, si no está en el hilo principal) o proporcione un controlador de finalización o use el enfoque de notificación en su clase de llamada.
shallowThought
¿Por qué llamas group.enterfuera del bloque asincrónico? ¿No debería ser responsabilidad de cada bloque entrar y salir del grupo?
Proyecto de ley
4
@ Bill waitespera hasta entery leavellamadas están equilibradas. Si pusiera enteren el cierre, waitno esperaría porque enteraún no ha sido llamado y por lo tanto el número de llamadas entery está balanceado (# enter == 0, # leav == 0). leave
shallowThought
1
@rustyMagnet para las pruebas, probablemente este no sea el camino a seguir. Utilice XCTTestExpectations en su lugar. Vea este código de muestra
shallowThought
26

En Swift 3, no hay necesidad de un controlador de finalización cuando DispatchQueuefinaliza una tarea. Además puedes lograr tu objetivo de diferentes formas

Una forma es esta:

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

Esperará hasta que finalice el ciclo, pero en este caso su hilo principal se bloqueará.

También puedes hacer lo mismo como esto:

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

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

        ////// do your remaining work
    }

Una última cosa: si desea usar completeHandler cuando su tarea se complete usando DispatchQueue, puede usar DispatchWorkItem.

A continuación, se muestra un ejemplo de cómo utilizarlo 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
fuente
1
Los probé todos y ninguno funcionó mientras intentaba manejar las llamadas de la base de fuego en un bucle for
2

Usar grupo de despacho

   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
fuente
5
Suponiendo que llame a esto en la cola principal, esto provocará un interbloqueo.
shallowThought
@shallowThought Tan cierto.
Prateekro
Quiero ejecutar una función en otra clase pero quiero esperar para terminar esa función y luego en la clase actual continuar, ¿cómo puedo manejar eso?
Saeed Rahmatolahi
1
Aunque el ejemplo anterior se bloqueará en el hilo principal, como se ha mostrado en las respuestas anteriores, envolver dentro DispatchQueue.global().async{}no bloqueará la cola principal.
JonnyB
2

Versión Swift 5 de la solución

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

Rápido 4

Puede utilizar la función Async para estas situaciones. Cuando lo usa DispatchGroup(), a veces puede ocurrir un interbloqueo .

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
fuente