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.

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 .


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
}

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.

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

}


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!)
        }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.