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
});
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
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!
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.
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.
DispatchGroup
, DispatchGroup
notify(qos:flags:queue:execute:)
et DispatchQueue
async(group:qos:flags:execute:)
methodLe 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
*/
DispatchGroup
, DispatchGroup
wait()
, DispatchGroup
enter()
et DispatchGroup
leave()
methodsimport 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:)
.
DispatchWorkItemFlags
barrier
property et DispatchQueue
DispatchQueue
async(group:qos:flags:execute:)
methodGrand 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 variableDispatchWorkItem
.
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
*/
DispatchWorkItem
, DispatchWorkItemFlags
barrier
property et DispatchQueue
async(execute:)
methodimport 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
*/
DispatchSemaphore
, DispatchSemaphore
wait()
et DispatchSemaphore
signal()
methodsSoroush 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 DispatchSemaphore
init(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
*/
OperationQueue
et BlockOperation
La référence de l'API de développeur Apple indique à propos de OperationQueue
:
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
*/
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];
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.
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();
}
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)
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
}
}