web-dev-qa-db-fra.com

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

Lorsque vous utilisez GCD, nous voulons attendre que deux blocs asynchrones soient exécutés et terminés avant de passer aux étapes suivantes de l'exécution. 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
});
174
tom

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

Votre exemple pourrait ressembler à quelque chose comme ça:

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 ceci:

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
284
Jörn Eyrich

En développant la réponse de Jörn Eyrich (sa réponse si vous augmentez celle-ci), si vous ne contrôlez pas les appels dispatch_async pour vos blocs, comme cela pourrait être le cas pour les blocs d'achèvement asynchrone, vous pouvez utiliser les groupes _dispatch_group_enter et dispatch_group_leave directement.

Dans cet exemple, nous prétendons que computeInBackground est quelque chose que nous ne pouvons pas modifier (imaginons qu'il s'agisse d'un rappel de délégué, NSURLConnection completionHandler ou autre), et que nous n'avons pas accès aux appels de dispatch.

// 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é sous la forme:

- (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 les horodatages 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!
260
ɲeuroburɳ

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, répartissez vos deux blocs, puis le dernier bloc avec barrière, ce qui le fera attendre que les deux autres soient terminés. 

56
Rob

Avec Swift 3, Grand Central Dispatch offre de nombreuses façons de résoudre votre problème. En fonction de vos besoins, vous pouvez choisir l’un des six modèles présentés dans les extraits de Playground suivants.


#1. Utilisation de DispatchGroup , DispatchGroupnotify(qos:flags:queue:execute:) et DispatchQueueasync(group:qos:flags:execute:) method

Le Guide de programmation de concurrents Apple Developer indique environ DispatchGroup :

Les groupes d'affectation 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 là où vous ne pouvez pas progresser tant que toutes les tâches spécifiées ne sont pas terminées. Par exemple, après avoir réparti plusieurs tâches pour calculer certaines données, vous pouvez utiliser un groupe pour attendre ces tâches, puis traiter les résultats à la fin.

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 , DispatchGroupwait() , DispatchGroupenter() et DispatchGroupleave() methods

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 DispatchGroupwait() avec DispatchQueueasync(group:qos:flags:execute:) ou mélanger DispatchGroupenter() et DispatchGroupleave() avec DispatchGroupnotify(qos:flags:queue:execute:).


# 3. Utilisation de Dispatch​Work​Item​Flagsbarrier property et DispatchQueueDispatchQueueasync(group:qos:flags:execute:) method

Grand Central Dispatch Tutorial pour Swift 3: Partie 1/2 article de Raywenderlich.com donne une définition de barrières} _:

Les barrières de répartition sont un groupe de fonctions agissant comme un goulot d'étranglement de type série lorsque vous travaillez avec des files d'attente simultanées. [...] Lorsque vous soumettez une DispatchWorkItem à une file d'attente de distribution, vous pouvez définir des indicateurs pour indiquer qu'il doit s'agir du seul élément exécuté dans la file d'attente spécifiée pour cette heure particulière. Cela signifie que tous les éléments soumis à la file d'attente avant la barrière de répartition doivent être terminés avant l'exécution de la variable DispatchWorkItem.

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. Utilisation de DispatchWorkItem , Dispatch​Work​Item​Flagsbarrier property et DispatchQueueasync(execute:) method

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. Utilisation de DispatchSemaphore , DispatchSemaphorewait() et DispatchSemaphoresignal() methods

Soroush Khanlou a écrit les lignes suivantes dans The GCD Handbook blog post:

À l'aide d'un sémaphore, nous pouvons bloquer un thread pour une durée arbitraire, jusqu'à ce qu'un signal provenant 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’une API asynchrone doit être rendue synchrone, mais vous ne pouvez pas la modifier.

La référence API des développeurs Apple donne également la discussion suivante sur DispatchSemaphoreinit(value:​) initializer:

Le passage à zéro pour la valeur est utile lorsque deux threads doivent réconcilier la fin d'un événement particulier. Il est utile de transmettre une valeur supérieure à zéro pour gérer un pool de ressources fini, 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. Utilisation de OperationQueue et BlockOperation

La référence de l'API de développeur Apple indique à propos de Operation​Queue:

Les files d’opérations utilisent la bibliothèque libdispatch (é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
 */
48
Imanou Petit

Je sais que vous avez posé des questions sur GCD, mais si vous le souhaitez, NSOperationQueue gère également ce genre de choses avec beaucoup de grâce, 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];
39
Rob

Les réponses ci-dessus sont toutes cool, mais elles ont toutes manqué une chose. Le groupe exécute les tâches (les 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 concurrente créée demoQueue. Si je ne crée aucune file d'attente, il s'exécute dans 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 y a une troisième façon de faire des tâches exécutées 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_async pour obtenir ce que vous voulez.

3
Bruce Lee

La première réponse est essentiellement correcte, mais si vous voulez le moyen le plus simple d’atteindre le résultat souhaité, voici un exemple de code autonome montrant comment le faire avec un sémaphore (qui explique également le fonctionnement en arrière-plan des groupes d’envoi, 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();
}
3
jkh

Réponse acceptée en Swift:

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)
1
Outsider

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
    }
 }
0
atereshkov