Esperando até que a tarefa termine

99

Como posso fazer meu código esperar até que a tarefa no DispatchQueue seja concluída? Ele precisa de algum CompletionHandler ou algo assim?

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

}

Estou usando o Xcode 8.2 e escrevendo em Swift 3.

Bartosz Woźniak
fonte

Respostas:

229

Use DispatchGroups para conseguir isso. Você pode ser notificado quando o grupo enter()e as leave()chamadas estiverem equilibrados:

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

ou você pode 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()bloqueia a fila atual (provavelmente a fila principal no seu caso), então você precisa dispatch.asyncem outra fila (como no código de exemplo acima) para evitar um deadlock .

raso pensamento
fonte
Eu quero executar uma função em outra classe, mas quero esperar para terminar essa função e, em seguida, na classe atual continuar, como posso lidar com isso?
Saeed Rahmatolahi
2
@SaeedRahmatolahi: Use a waitabordagem (se não for problema para você bloquear, ou seja, se você não estiver no thread principal) ou forneça um manipulador de conclusão ou use a abordagem de notificação em sua classe de chamada.
shallowThought
Por que você chama group.enterfora do bloco assíncrono? Não deveria ser responsabilidade de cada bloco entrar e sair do grupo?
Bill
4
@ Bill waitespera até entere leavechamadas são equilibradas. Se você colocar enterno fechamento, waitnão esperaria porque enterainda não foi chamado e, portanto, o número de chamadas entere está balanceado (# enter == 0, # leav == 0). leave
shallowThought
1
@rustyMagnet para testes, provavelmente este não é o caminho a seguir. Em XCTTestExpectationvez disso, use s. Veja este código de amostra
shallowThought
26

No Swift 3, não há necessidade de manipulador de conclusão quando DispatchQueue terminar uma tarefa. Além disso, você pode atingir seu objetivo de maneiras diferentes

Uma maneira é 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)")

Ele esperará até que o loop termine, mas neste caso seu thread principal será bloqueado.

Você também pode fazer a mesma coisa assim:

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

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

        ////// do your remaining work
    }

Uma última coisa: se você deseja usar a conclusãoHandler quando sua tarefa for concluída usando o DispatchQueue, você pode usar DispatchWorkItem.

Aqui está um exemplo de como usar 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
fonte
1
Eu tentei todos eles e nenhum funcionou ao tentar lidar com chamadas Firebase em um loop 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
fonte
5
Supondo que você chame isso na fila principal, isso causará um deadlock.
shallowThought
@shallowThought Tão verdadeiro.
Prateekro
Eu quero executar uma função em outra classe, mas quero esperar para terminar essa função e, em seguida, na classe atual continuar, como posso lidar com isso?
Saeed Rahmatolahi
1
Embora o exemplo acima bloqueie no thread principal, como as respostas anteriores mostraram, agrupar dentro DispatchQueue.global().async{}não bloqueará a fila principal.
JonnyB de
2

Versão Swift 5 da solução

função 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
fonte
1

Swift 4

Você pode usar a função assíncrona para essas situações. Quando você usa DispatchGroup(), às vezes pode ocorrer um deadlock .

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
fonte