Attendre que deux blocs asynchrones soient exécutés avant de démarrer un autre bloc

192

Lors de l'utilisation de GCD, nous voulons attendre que deux blocs asynchrones soient exécutés et terminés avant de passer aux étapes d'exécution suivantes. Quelle est la meilleure façon de le faire?

Nous avons essayé ce qui suit, mais cela ne semble pas fonctionner:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block1
});


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block2
});

// wait until both the block1 and block2 are done before start block3
// how to do that?

dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block3
});
à M
la source
Voir ma réponse pour Swift 5 qui offre jusqu'à six façons différentes de résoudre votre problème.
Imanou Petit

Réponses:

301

Utiliser des groupes de répartition: voir ici un exemple, "En attente de groupes de tâches en file d'attente" dans le chapitre "Files de répartition" du Guide de programmation de l'accès concurrentiel de la bibliothèque pour développeurs iOS d'Apple

Votre exemple pourrait ressembler à ceci:

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block1
    NSLog(@"Block1");
    [NSThread sleepForTimeInterval:5.0];
    NSLog(@"Block1 End");
});


dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block2
    NSLog(@"Block2");
    [NSThread sleepForTimeInterval:8.0];
    NSLog(@"Block2 End");
});

dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
    // block3
    NSLog(@"Block3");
});

// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group);

et pourrait produire une sortie comme celle-ci:

2012-08-11 16:10:18.049 Dispatch[11858:1e03] Block1
2012-08-11 16:10:18.052 Dispatch[11858:1d03] Block2
2012-08-11 16:10:23.051 Dispatch[11858:1e03] Block1 End
2012-08-11 16:10:26.053 Dispatch[11858:1d03] Block2 End
2012-08-11 16:10:26.054 Dispatch[11858:1d03] Block3
Jörn Eyrich
la source
3
Cool. les tâches / blocs asynchrones, une fois associés au groupe, seront-ils exécutés séquentiellement ou simultanément? Je veux dire, supposons que block1 et block2 sont associés à un groupe maintenant, block2 attendra-t-il que block1 soit terminé avant de pouvoir commencer à s'exécuter?
tom
9
C'est à toi de voir. dispatch_group_asyncest juste comme dispatch_asyncavec un paramètre de groupe ajouté. Ainsi, si vous utilisez des files d'attente différentes pour block1 et block2 ou que vous les planifiez sur la même file d'attente simultanée, elles peuvent s'exécuter simultanément; si vous les programmez sur la même file d'attente série, ils s'exécuteront en série. Ce n'est pas différent de la planification des blocs sans groupes.
Jörn Eyrich
1
Cela s'applique-t-il également à l'exécution de la publication de service Web?
SleepNot
Avez-vous remarqué que l'heure n'est pas égale à l'heure de sommeil définie dans votre bloc? pourquoi ce serait comme ça?
Damon Yuan
2
Dans ARC, supprimez simplement dispatch_release (group);
loretoparisi
272

En développant la réponse de Jörn Eyrich (augmentez sa réponse si vous votez pour celle-ci), si vous n'avez pas de contrôle sur les dispatch_asyncappels pour vos blocs, comme cela pourrait être le cas pour les blocs de complétion asynchrones, vous pouvez utiliser les groupes GCD en utilisant dispatch_group_enteretdispatch_group_leave directement.

Dans cet exemple, nous prétendons computeInBackgroundêtre quelque chose que nous ne pouvons pas changer (imaginez que c'est un rappel de délégué, NSURLConnection completionHandler, ou autre), et donc nous n'avons pas accès aux appels de répartition.

// create a group
dispatch_group_t group = dispatch_group_create();

// pair a dispatch_group_enter for each dispatch_group_leave
dispatch_group_enter(group);     // pair 1 enter
[self computeInBackground:1 completion:^{
    NSLog(@"1 done");
    dispatch_group_leave(group); // pair 1 leave
}];

// again... (and again...)
dispatch_group_enter(group);     // pair 2 enter
[self computeInBackground:2 completion:^{
    NSLog(@"2 done");
    dispatch_group_leave(group); // pair 2 leave
}];

// Next, setup the code to execute after all the paired enter/leave calls.
//
// Option 1: Get a notification on a block that will be scheduled on the specified queue:
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"finally!");
});

// Option 2: Block an wait for the calls to complete in code already running
// (as cbartel points out, be careful with running this on the main/UI queue!):
//
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // blocks current thread
// NSLog(@"finally!");

Dans cet exemple, computeInBackground: completion: est implémenté comme:

- (void)computeInBackground:(int)no completion:(void (^)(void))block {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"%d starting", no);
        sleep(no*2);
        block();
    });
}

Sortie (avec horodatage d'une exécution):

12:57:02.574  2 starting
12:57:02.574  1 starting
12:57:04.590  1 done
12:57:06.590  2 done
12:57:06.591  finally!
ɲeuroburɳ
la source
1
@ ɲeuroburɳ Le code ci-dessus attend sur le thread principal. Je pense que cela bloquera le fil principal et empêchera l'interface utilisateur jusqu'à ce que tout le groupe soit complet. Je recommande de déplacer l'attente vers un fil d'arrière-plan. Par exemple, dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, 0)
cbartel
2
@cbartel, bonne prise! J'ai mis à jour l'exemple de code pour refléter votre commentaire. Plusieurs fois, vous avez besoin que le rappel soit sur la file d'attente principale - dans ce cas, bien que le dispatch_queue_notifysoit probablement mieux (à moins que le temps de blocage ne soit garanti court).
ɲeuroburɳ
Où puis-je libérer le groupe (c'est-à-dire dispatch_release (group))? Je ne sais pas s'il est sûr de publier dans dispatch_group_notify. Mais comme c'est le code qui est exécuté une fois le groupe terminé, je ne sais pas où publier.
GingerBreadMane
Si vous utilisez ARC, vous n'avez pas besoin d'appeler dispatch_release: stackoverflow.com/questions/8618632/…
ɲeuroburɳ
3
Bel article qui explique en outre que: commandshift.co.uk/blog/2014/03/19/…
Rizon
98

Avec Swift 5.1, Grand Central Dispatch offre de nombreuses façons de résoudre votre problème. Selon vos besoins, vous pouvez choisir l'un des sept modèles illustrés dans les extraits Playground suivants.


#1. En utilisant DispatchGroup, DispatchGroup's notify(qos:flags:queue:execute:)et DispatchQueued'async(group:qos:flags:execute:)

Le guide de programmation Apple Developer Concurrency indique à propos deDispatchGroup :

Les groupes de répartition sont un moyen de bloquer un thread jusqu'à ce qu'une ou plusieurs tâches aient fini de s'exécuter. Vous pouvez utiliser ce comportement dans les endroits où vous ne pouvez pas progresser jusqu'à ce que toutes les tâches spécifiées soient terminées. Par exemple, après avoir distribué plusieurs tâches pour calculer certaines données, vous pouvez utiliser un groupe pour attendre ces tâches, puis traiter les résultats lorsqu'ils sont terminés.

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()

queue.async(group: group) {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async(group: group) {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

group.notify(queue: queue) {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 2. Utilisation de DispatchGroup, DispatchGroup's wait(), DispatchGroup' s enter()et DispatchGroup'sleave()

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let group = DispatchGroup()

group.enter()
queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
    group.leave()
}

group.enter()
queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
    group.leave()
}

queue.async {
    group.wait()
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

Notez que vous pouvez également mélanger DispatchGroup wait()avec DispatchQueue async(group:qos:flags:execute:)ou mélanger DispatchGroup enter()et DispatchGroup leave()avec DispatchGroup notify(qos:flags:queue:execute:).


# 3. L' utilisation et l »Dispatch​Work​Item​Flags barrierDispatchQueueasync(group:qos:flags:execute:)

Grand Central Dispatch Tutorial for Swift 4: L' article de la partie 1/2 de Raywenderlich.com donne une définition des barrières :

Les barrières de répartition sont un groupe de fonctions agissant comme un goulot d'étranglement de style série lors de l'utilisation de files d'attente simultanées. Lorsque vous soumettez un DispatchWorkItemà une file d'attente de distribution, vous pouvez définir des indicateurs pour indiquer qu'il doit être le seul élément exécuté sur la file d'attente spécifiée pour cette heure particulière. Cela signifie que tous les articles soumis à la file d'attente avant la barrière d'expédition doivent être terminés avant leDispatchWorkItem exécution testament.

Usage:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

queue.async(flags: .barrier) {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 4. En utilisant DispatchWorkItem, Dispatch​Work​Item​Flags's barrieret DispatchQueued'async(execute:)

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

let dispatchWorkItem = DispatchWorkItem(qos: .default, flags: .barrier) {
    print("#3 finished")
}

queue.async(execute: dispatchWorkItem)

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 5. En utilisant DispatchSemaphore, DispatchSemaphore's wait()et DispatchSemaphored'signal()

Soroush Khanlou a écrit les lignes suivantes dans le billet de blog The GCD Handbook :

En utilisant un sémaphore, nous pouvons bloquer un thread pendant une durée arbitraire, jusqu'à ce qu'un signal d'un autre thread soit envoyé. Les sémaphores, comme le reste de GCD, sont thread-safe et peuvent être déclenchés de n'importe où. Les sémaphores peuvent être utilisés lorsqu'il existe une API asynchrone que vous devez rendre synchrone, mais que vous ne pouvez pas la modifier.

La référence de l'API Apple Developer donne également la discussion suivante pour l' DispatchSemaphore init(value:​)initialiseur:

La transmission de zéro pour la valeur est utile lorsque deux threads doivent réconcilier l'achèvement d'un événement particulier. Passer une valeur supérieure à zéro est utile pour gérer un pool fini de ressources, où la taille du pool est égale à la valeur.

Usage:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let queue = DispatchQueue(label: "com.company.app.queue", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 0)

queue.async {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
    semaphore.signal()
}

queue.async {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
    semaphore.signal()
}

queue.async {
    semaphore.wait()
    semaphore.wait()    
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 */

# 6. L' utilisation OperationQueueet Operationl »addDependency(_:)

La référence de l'API Apple Developer indique Operation​Queue:

Les files d'attente d'opérations utilisent la libdispatchbibliothèque (également appelée Grand Central Dispatch) pour lancer l'exécution de leurs opérations.

Usage:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let operationQueue = OperationQueue()

let blockOne = BlockOperation {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

let blockTwo = BlockOperation {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

let blockThree = BlockOperation {
    print("#3 finished")
}

blockThree.addDependency(blockOne)
blockThree.addDependency(blockTwo)

operationQueue.addOperations([blockThree, blockTwo, blockOne], waitUntilFinished: false)

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 or
 #2 started
 #1 started
 #2 finished
 #1 finished
 #3 finished
 */

#7. L' utilisation OperationQueueet OperationQueuel » addBarrierBlock(_:)(nécessite iOS 13)

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let operationQueue = OperationQueue()

let blockOne = BlockOperation {
    print("#1 started")
    Thread.sleep(forTimeInterval: 5)
    print("#1 finished")
}

let blockTwo = BlockOperation {
    print("#2 started")
    Thread.sleep(forTimeInterval: 2)
    print("#2 finished")
}

operationQueue.addOperations([blockTwo, blockOne], waitUntilFinished: false)
operationQueue.addBarrierBlock {
    print("#3 finished")
}

/*
 prints:
 #1 started
 #2 started
 #2 finished
 #1 finished
 #3 finished
 or
 #2 started
 #1 started
 #2 finished
 #1 finished
 #3 finished
 */
Imanou Petit
la source
Existe-t-il une solution pour les appels asynchrones sans utiliser group.enter () et group.leave () pour chacun (et sans sémaphores)? Comme si je dois attendre une demande asynchrone à un serveur, attendez ensuite une deuxième demande asynchrone et ainsi de suite. J'ai lu cet article avanderlee.com/swift/asynchronous-operations mais je n'en vois pas une utilisation simple par rapport à BlockOperation
Woof
58

Une autre alternative GCD est un obstacle:

dispatch_queue_t queue = dispatch_queue_create("com.company.app.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{ 
    NSLog(@"start one!\n");  
    sleep(4);  
    NSLog(@"end one!\n");
});

dispatch_async(queue, ^{  
    NSLog(@"start two!\n");  
    sleep(2);  
    NSLog(@"end two!\n"); 
});

dispatch_barrier_async(queue, ^{  
    NSLog(@"Hi, I'm the final block!\n");  
});

Créez simplement une file d'attente simultanée, distribuez vos deux blocs, puis envoyez le bloc final avec barrière, ce qui le fera attendre que les deux autres se terminent.

Rob
la source
Y a-t-il un problème si je n'ai pas utilisé le sommeil (4);
Himanth
Non, bien sûr, cela ne pose aucun problème. En fait, vous ne voulez pratiquement jamais sleep()! Je n'ai ajouté ces sleep()appels que pour des raisons pédagogiques, pour faire fonctionner les blocs suffisamment longtemps pour que vous puissiez voir qu'ils s'exécutent simultanément. Dans cet exemple trivial, en l'absence de sleep(), ces deux blocs peuvent s'exécuter si rapidement que le bloc distribué peut commencer et se terminer avant que vous n'ayez une chance d'observer empiriquement l'exécution simultanée. Mais pas sleep()dans votre propre code.
Rob
39

Je sais que vous avez posé des questions sur GCD, mais si vous le souhaitez, NSOperationQueuegère également ce genre de choses très gracieusement, par exemple:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 3");
}];

NSOperation *operation;

operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 1");
    sleep(7);
    NSLog(@"Finishing 1");
}];

[completionOperation addDependency:operation];
[queue addOperation:operation];

operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Starting 2");
    sleep(5);
    NSLog(@"Finishing 2");
}];

[completionOperation addDependency:operation];
[queue addOperation:operation];

[queue addOperation:completionOperation];
Rob
la source
3
Cela convient lorsque le code à l'intérieur de votre NSBlockOperation est synchrone. Mais que se passe-t-il si ce n'est pas le cas et que vous souhaitez déclencher l'achèvement lorsque votre opération asynchrone est terminée?
Greg Maletic
3
@GregMaletic Dans ce cas, je crée une NSOperationsous-classe qui est simultanée et définie isFinishedlorsque le processus asynchrone se termine. Ensuite, les dépendances fonctionnent correctement.
Rob
1
@GregMaletic Oui, vous pouvez également l'utiliser (tant que cela dispatch_semaphore_waitn'a pas lieu dans la file d'attente principale et tant que vos signaux et vos attentes sont équilibrés). Tant que vous ne bloquez pas la file d'attente principale, une approche sémaphore convient si vous n'avez pas besoin de la flexibilité des opérations (par exemple, avoir la possibilité de les annuler, la capacité de contrôler le degré de concurrence, etc.).
Rob
1
@ Reza.Ab - Si vous avez besoin que la tâche 1 se termine avant le début de la tâche 2, ajoutez une dépendance entre ces tâches. Ou si la file d'attente n'effectue toujours qu'une seule tâche à la fois, faites-en une file d'attente série en définissant maxConcurrentOperationCountsur 1. Vous pouvez également définir la priorité des opérations, à la fois les qualityOfServiceet queuePriority, mais ceux-ci ont un impact beaucoup plus subtil sur la priorité des tâches que les dépendances et / ou le degré de concurrence de la file d'attente.
Rob
4

Les réponses ci-dessus sont toutes cool, mais elles ont toutes manqué une chose. group exécute des tâches (blocs) dans le thread où il est entré lorsque vous utilisez dispatch_group_enter/ dispatch_group_leave.

- (IBAction)buttonAction:(id)sender {
      dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
      dispatch_async(demoQueue, ^{
        dispatch_group_t demoGroup = dispatch_group_create();
        for(int i = 0; i < 10; i++) {
          dispatch_group_enter(demoGroup);
          [self testMethod:i
                     block:^{
                       dispatch_group_leave(demoGroup);
                     }];
        }

        dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
          NSLog(@"All group tasks are done!");
        });
      });
    }

    - (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
      NSLog(@"Group task started...%ld", index);
      NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
      [NSThread sleepForTimeInterval:1.f];

      if(completeBlock) {
        completeBlock();
      }
    }

cela s'exécute dans la file d'attente simultanée créée demoQueue. Si je ne crée aucune file d'attente, il s'exécute dans le thread principal .

- (IBAction)buttonAction:(id)sender {
    dispatch_group_t demoGroup = dispatch_group_create();
    for(int i = 0; i < 10; i++) {
      dispatch_group_enter(demoGroup);
      [self testMethod:i
                 block:^{
                   dispatch_group_leave(demoGroup);
                 }];
    }

    dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
      NSLog(@"All group tasks are done!");
    });
    }

    - (void)testMethod:(NSInteger)index block:(void(^)(void))completeBlock {
      NSLog(@"Group task started...%ld", index);
      NSLog(@"Current thread is %@ thread", [NSThread isMainThread] ? @"main" : @"not main");
      [NSThread sleepForTimeInterval:1.f];

      if(completeBlock) {
        completeBlock();
      }
    }

et il existe une troisième façon d'exécuter des tâches dans un autre thread:

- (IBAction)buttonAction:(id)sender {
      dispatch_queue_t demoQueue = dispatch_queue_create("com.demo.group", DISPATCH_QUEUE_CONCURRENT);
      //  dispatch_async(demoQueue, ^{
      __weak ViewController* weakSelf = self;
      dispatch_group_t demoGroup = dispatch_group_create();
      for(int i = 0; i < 10; i++) {
        dispatch_group_enter(demoGroup);
        dispatch_async(demoQueue, ^{
          [weakSelf testMethod:i
                         block:^{
                           dispatch_group_leave(demoGroup);
                         }];
        });
      }

      dispatch_group_notify(demoGroup, dispatch_get_main_queue(), ^{
        NSLog(@"All group tasks are done!");
      });
      //  });
    }

Bien sûr, comme mentionné, vous pouvez utiliser dispatch_group_asyncpour obtenir ce que vous voulez.

Bruce Lee
la source
3

La première réponse est essentiellement correcte, mais si vous voulez le moyen le plus simple d'obtenir le résultat souhaité, voici un exemple de code autonome montrant comment le faire avec un sémaphore (qui est également comment les groupes de répartition fonctionnent en arrière-plan, JFYI) :

#include <dispatch/dispatch.h>
#include <stdio.h>

main()
{
        dispatch_queue_t myQ = dispatch_queue_create("my.conQ", DISPATCH_QUEUE_CONCURRENT);
        dispatch_semaphore_t mySem = dispatch_semaphore_create(0);

        dispatch_async(myQ, ^{ printf("Hi I'm block one!\n"); sleep(2); dispatch_semaphore_signal(mySem);});
        dispatch_async(myQ, ^{ printf("Hi I'm block two!\n"); sleep(4); dispatch_semaphore_signal(mySem);});
        dispatch_async(myQ, ^{ dispatch_semaphore_wait(mySem, DISPATCH_TIME_FOREVER); printf("Hi, I'm the final block!\n"); });
        dispatch_main();
}
jkh
la source
7
Deux observations: 1. Il vous manque un dispatch_semaphore_wait. Vous avez deux signaux, vous avez donc besoin de deux attentes. Tel quel, votre bloc "d'achèvement" commencera dès que le premier bloc signale le sémaphore, mais avant que l'autre bloc ne se termine; 2. Étant donné qu'il s'agissait d'une question iOS, je découragerais l'utilisation de dispatch_main.
Rob
1
Je suis d'accord avec Rob. Ce n'est pas une solution valable. Le dispatch_semaphore_waitdébloquera dès que l'une des dispatch_semaphore_signalméthodes sera appelée. La raison pour laquelle cela peut sembler fonctionner est que les printfblocs pour «un» et «deux» se produisent immédiatement, et que printfpour le «finalement» survient après une attente - donc après que le bloc a dormi pendant 2 secondes. Si vous mettez le printf après les sleepappels, vous obtiendrez la sortie pour «un», puis 2 secondes plus tard pour «enfin», puis 2 secondes plus tard pour «deux».
ɲeuroburɳ
1

Réponse acceptée rapidement:

let group = DispatchGroup()

group.async(group: DispatchQueue.global(qos: .default), execute: {
    // block1
    print("Block1")
    Thread.sleep(forTimeInterval: 5.0)
    print("Block1 End")
})


group.async(group: DispatchQueue.global(qos: .default), execute: {
    // block2
    print("Block2")
    Thread.sleep(forTimeInterval: 8.0)
    print("Block2 End")
})

dispatch_group_notify(group, DispatchQueue.global(qos: .default), {
    // block3
    print("Block3")
})

// only for non-ARC projects, handled automatically in ARC-enabled projects.
dispatch_release(group)

la source
0

Exemple de Swift 4.2:

let group = DispatchGroup.group(count: 2)
group.notify(queue: DispatchQueue.main) {
     self.renderingLine = false
     // all groups are done
}
DispatchQueue.main.async {
    self.renderTargetNode(floorPosition: targetPosition, animated: closedContour) {
        group.leave()
        // first done
    }
    self.renderCenterLine(position: targetPosition, animated: closedContour) {
        group.leave()
        // second done
    }
 }
aterechkov
la source
group.leave()a causé un crash
Ben
-3

Pour ne pas dire que les autres réponses ne sont pas idéales dans certaines circonstances, mais voici un extrait que j'utilise toujours depuis Google:

- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {


    if (signInDoneSel) {
        [self performSelector:signInDoneSel];
    }

}
ChuckKelly
la source