Esperando hasta que se ejecuten dos bloques asíncronos antes de comenzar otro bloque

Al usar GCD, queremos esperar hasta que se ejecuten y ejecuten dos bloques asíncronos antes de pasar a los siguientes pasos de ejecución. ¿Cuál es la mejor manera de hacer eso?

Probamos lo siguiente, pero parece que no funciona:

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 }); 

Usar grupos de despacho: consulte aquí para ver un ejemplo, “Esperando en grupos de tareas en cola” en el capítulo “Cola de envío” de la Guía de progtwigción de simultaneidad de la Biblioteca del desarrollador iOS de Apple

Tu ejemplo podría verse más o menos así:

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

y podría producir resultados como este:

 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 

Ampliando la respuesta de Jörn Eyrich (inviertan su respuesta si votó positivamente esta), si no tiene control sobre las llamadas dispatch_async para sus bloques, como podría ser el caso para los bloques de finalización asincrónicos, puede usar los grupos GCD usando dispatch_group_enter y dispatch_group_leave directamente.

En este ejemplo, estamos pretendiendo que computeInBackground es algo que no podemos cambiar (imagina que es una callback de delegado, NSURLConnection completionHandler, o lo que sea), y por lo tanto no tenemos acceso a las llamadas de despacho.

 // 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 paird 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!"); 

En este ejemplo, computeInBackground: completion: se implementa como:

 - (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(); }); } 

Salida (con marcas de tiempo de una ejecución):

 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! 

Otra alternativa de GCD es una barrera:

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

Simplemente crea una cola concurrente, envía tus dos bloques y luego envía el bloque final con barrera, lo que hará que espere a que los otros dos terminen.

Con Swift 3, Grand Central Dispatch ofrece muchas formas de resolver su problema. De acuerdo con sus necesidades, puede elegir uno de los seis patrones que se muestran en los siguientes fragmentos de Playground.


# 1. Usando DispatchGroup , DispatchGroup notify(qos:flags:queue:execute:) y DispatchQueue async(group:qos:flags:execute:) methods

La Guía de Progtwigción de Concurrencia de Desarrollador de Apple establece sobre DispatchGroup :

Los grupos de envío son una forma de bloquear un hilo hasta que una o más tareas finalicen la ejecución. Puede usar este comportamiento en lugares donde no puede avanzar hasta que se completen todas las tareas especificadas. Por ejemplo, después de enviar varias tareas para calcular algunos datos, puede usar un grupo para esperar esas tareas y luego procesar los resultados cuando terminen.

 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. Utilizando los DispatchGroup , DispatchGroup wait() , DispatchGroup enter() y DispatchGroup leave()

 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 */ 

Tenga en cuenta que también puede mezclar DispatchGroup wait() con DispatchQueue async(group:qos:flags:execute:) o mix DispatchGroup enter() y DispatchGroup leave() con DispatchGroup notify(qos:flags:queue:execute:) .


# 3. Uso de la propiedad de barrier async(group:qos:flags:execute:) Dispatch​Work​Item​Flags y del método DispatchQueue DispatchQueue async(group:qos:flags:execute:)

El tutorial de Grand Central Dispatch para Swift 3: el artículo de la Parte 1/2 de Raywenderlich.com brinda una definición de barreras :

Las barreras de despacho son un grupo de funciones que actúa como un cuello de botella de estilo serial cuando se trabaja con colas concurrentes. […] Cuando envía un DispatchWorkItem a una cola de despacho puede establecer indicadores para indicar que debe ser el único elemento ejecutado en la cola especificada para ese momento en particular. Esto significa que todos los artículos enviados a la cola antes de la barrera de envío deben completarse antes de que se ejecute DispatchWorkItem .

Uso:

 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. Utilizando DispatchWorkItem , propiedad de barrier Dispatch​Work​Item​Flags y método DispatchQueue 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. Utilizando los métodos DispatchSemaphore , DispatchSemaphore wait() y DispatchSemaphore signal()

Soroush Khanlou escribió las siguientes líneas en la publicación del blog The GCD Handbook :

Usando un semáforo, podemos bloquear un hilo por una cantidad arbitraria de tiempo, hasta que se envíe una señal desde otro hilo. Los semáforos, al igual que el rest de GCD, son seguros para subprocesos y se pueden activar desde cualquier lugar. Los semáforos se pueden usar cuando hay una API asíncrona que debe sincronizar, pero no puede modificarla.

La Referencia de la API para desarrolladores de Apple también brinda la siguiente discusión para el init(value:​) DispatchSemaphore init(value:​) :):

Pasar cero para el valor es útil para cuando dos hilos necesitan conciliar la finalización de un evento en particular. Pasar un valor mayor que cero es útil para administrar un grupo finito de recursos, donde el tamaño del grupo es igual al valor.

Uso:

 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. Usando OperationQueue y BlockOperation

La Referencia de la API de desarrollador de Apple indica acerca de Operation​Queue :

Las colas de operaciones utilizan la biblioteca libdispatch (también conocida como Grand Central Dispatch) para iniciar la ejecución de sus operaciones.

Uso:

 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 */ 

Sé que preguntaste sobre GCD, pero si quisieras, NSOperationQueue también maneja este tipo de cosas con gran gracia, por ejemplo:

 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]; 

La primera respuesta es esencialmente correcta, pero si quiere la manera más simple de lograr el resultado deseado, aquí hay un ejemplo de código autónomo que demuestra cómo hacerlo con un semáforo (que es también la forma en que los grupos de despacho trabajan detrás de escena, JFYI) :

 #include  #include  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(); } 

Las respuestas anteriores son geniales, pero todas olvidaron una cosa. group ejecuta tareas (bloques) en la secuencia donde ingresó cuando usa 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(); } } 

esto se ejecuta en la cola concurrente creada demoQueue . Si no creo ninguna cola, se ejecuta en el hilo 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(); } } 

y hay una tercera forma de hacer que las tareas se ejecuten en otro hilo:

 - (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!"); }); // }); } 

Por supuesto, como se mencionó, puede usar dispatch_group_async para obtener lo que desea.

No decir que otras respuestas no son buenas para ciertas circunstancias, pero este es un fragmento que siempre utilizo de Google:

 - (void)runSigninThenInvokeSelector:(SEL)signInDoneSel { if (signInDoneSel) { [self performSelector:signInDoneSel]; } } 
Intereting Posts