Attendre la fin de la tâche

99

Comment puis-je faire attendre mon code jusqu'à la fin de la tâche dans DispatchQueue? A-t-il besoin de CompletionHandler ou de quelque chose?

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

}

J'utilise Xcode 8.2 et j'écris dans Swift 3.

Bartosz Woźniak
la source

Réponses:

229

Utilisez DispatchGroups pour y parvenir. Vous pouvez soit être averti lorsque le groupe enter()et les leave()appels sont équilibrés:

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 vous pouvez attendre:

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
}

Remarque : group.wait()bloque la file d'attente actuelle (probablement la file d'attente principale dans votre cas), vous devez donc dispatch.asyncle faire sur une autre file d'attente (comme dans l'exemple de code ci-dessus) pour éviter un blocage .

peu profonde
la source
Je veux exécuter une fonction dans une autre classe mais je veux attendre la fin de cette fonction, puis dans la classe actuelle continuer comment puis-je gérer cela?
Saeed Rahmatolahi
2
@SaeedRahmatolahi: Soit utilisez l' waitapproche (si ce n'est pas un problème pour vous de bloquer, c'est-à-dire si vous n'êtes pas sur le thread principal) ou fournissez un gestionnaire d'achèvement ou utilisez l'approche notify dans votre classe appelante.
shallowThought
Pourquoi appelez-vous en group.enterdehors du bloc asynchrone? Ne devrait-il pas être de la responsabilité de chaque bloc d'entrer et de sortir du groupe?
Bill
4
@Bill waitattend enteret les leaveappels sont équilibrés. Si vous mettez enterdans la fermeture, waitn'attendrait pas car entern'a pas encore été appelé et donc le nombre d' appels enteret sont équilibrés (# enter == 0, # leav == 0). leave
shallowThought
1
@rustyMagnet pour les tests, ce n'est probablement pas la voie à suivre. Utilisez XCTTestExpectationplutôt s. Voir cet exemple de code
shallowThought
26

Dans Swift 3, il n'y a pas besoin de gestionnaire d'achèvement lorsque vous avez DispatchQueueterminé une tâche. De plus, vous pouvez atteindre votre objectif de différentes manières

Une façon est la suivante:

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

Il attendra la fin de la boucle, mais dans ce cas, votre thread principal se bloquera.

Vous pouvez également faire la même chose comme ceci:

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

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

        ////// do your remaining work
    }

Une dernière chose: si vous souhaitez utiliser completionHandler lorsque votre tâche se termine à l'aide de DispatchQueue, vous pouvez utiliser DispatchWorkItem.

Voici un exemple d'utilisation 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
la source
1
Je les ai tous essayés et aucun n'a fonctionné en essayant de gérer les appels Firebase dans une boucle for
2

Utiliser le groupe d'expédition

   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
la source
5
En supposant que vous appelez cela sur la file d'attente principale, cela entraînera un blocage.
shallowThought
@shallowThought Si vrai.
Prateekro
Je veux exécuter une fonction dans une autre classe mais je veux attendre la fin de cette fonction, puis dans la classe actuelle continuer comment puis-je gérer cela?
Saeed Rahmatolahi
1
Bien que l'exemple ci-dessus bloque sur le thread principal, comme l'ont montré les réponses précédentes, l'enroulement à l'intérieur DispatchQueue.global().async{}ne bloquera pas la file d'attente principale.
JonnyB
2

Version Swift 5 de la solution

func myCriticalFunction () {var value1: String? var valeur2: Chaîne?

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
la source
1

Swift 4

Vous pouvez utiliser la fonction Async dans ces situations. Lorsque vous utilisez DispatchGroup(), un blocage peut parfois se produire.

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
la source