dispatch_group_wait con GCD

Así que estoy publicando una serie de imágenes en mi server. Quiero usar GCD para publicar la matriz de forma asíncrona, pero también quiero hacer que el método en el que esto sucede sea sincrónico para que pueda devolver un solo object de respuesta. Sin embargo, el método dispatch_group_wait parece estar regresando inmediatamente (y sin esperar a que mis bloques terminen). ¿Esto es un problema porque estoy usando un bloque dentro de un bloque?

NSArray *keys = [images allKeys]; __block NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]]; dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (int i = 0; i < [keys count]; i++) { __block NSString *key = [keys objectAtIndex:i]; dispatch_group_async(group, queue, ^{ [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){ @synchronized(responses){ if ([response succeeded]) { NSString *value = [[response data] objectForKey:@"image_token"]; [responses setObject:value forKey:key]; NSLog(@"inside success %@",responses); } else { NSString *error = response.displayableError; if (!error) { error = @"Sorry something went wrong, please try again later."; } [responses setObject:error forKey:@"error"]; [responses setObject:response forKey:@"response"]; } } }]; }); } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); 

Quiero simplemente esperar a que todos los methods [self postImage] realicen una callback desde el server y modifique el dictionary de respuestas.

El ejemplo de semáforo de Jonathan está en blanco. Sin embargo, mencioné usar una variable de condición como alternativa, así que pensé que al less podría publicar un ejemplo. En general, un CV se puede utilizar para esperar en condiciones más generales que no sean solo N trabajadores.

Tenga en count que las variables de condición tienen su lugar (aunque no necesariamente aquí), por lo general mejor cuando un locking ya es necesario para mutar el estado compartido, entonces otros hilos pueden esperar una condición específica.

 NSUInteger numKeys = [keys count]; NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:numKeys]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (NSUInteger i = 0; i < numKeys; i++) { __block NSString *key = [keys objectAtIndex:i]; dispatch_async(queue, ^{ [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){ // Basically, nothing more than a obtaining a lock // Use this as your synchronization primitive to serialize access // to the condition variable and also can double as primitive to replace // @synchronize -- if you feel that is still necessary [conditionLock lock]; ...; // When unlocking, decrement the condition counter [conditionLock unlockWithCondition:[conditionLock condition]-1]; }]; }); } // This call will get the lock when the condition variable is equal to 0 [conditionLock lockWhenCondition:0]; // You have mutex access to the shanetworking stuff... but you are the only one // running, so can just immediately release... [conditionLock unlock]; 

Sin ver el código de -postImage:completionHandler: es difícil decir dónde están progtwigdas las cosas, pero supongo que recurren a algo provisto por iOS. De ser así, los bloques del manejador dentro de su bloque se envían a la queue global de manera asíncrona, y luego la function o método suministrado por iOS vuelve de inmediato. En lo que respecta a su grupo de despacho, el trabajo se realiza casi instantáneamente.

No hay manera fácil de hacer que el grupo espere un trabajo que aún no está progtwigdo para cuando se llame a dispatch_group_wait() . Sin embargo, podemos agregar un gestor de cosas de nivel inferior llamado semáforo que garantiza que nuestras acciones se completan en el order correcto y progtwigrlo fuera del scope de los bloques internos (asíncronos).

 NSUInteger numKeys = [keys count]; dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (NSUInteger i = 0; i < numKeys; i++) { __block NSString *key = [keys objectAtIndex:i]; dispatch_group_async(group, queue, ^{ // We create a semaphore for each block here. More on that in a moment. // The initial count of the semaphore is 1, meaning that a signal must happen // before a wait will return. dispatch_semaphore_t sem = dispatch_semaphore_create(1); dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){ ... // This instructs one caller (ie the outer block) waiting on this semaphore to resume. dispatch_semaphore_signal(sem); }]; // This instructs the block to wait until signalled (ie at the end of the inner block.) dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // Done with the semaphore. Nothing special here. dispatch_release(sem); }); } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // Now all the tasks should have completed. dispatch_release(group); 

Sin embargo, hay un problema aquí. Los semáforos son un recurso del kernel . ¿Qué sucede si tenemos 100 tareas para realizar, pero el kernel solo puede proporcionar 99 semáforos? Las malas cosas pasan. Podemos rebuild el código para usar solo un semáforo, aunque a la espera de que aparezca un poco raro. Hacerlo realmente obvia al grupo de despachos por completo, por lo que básicamente estamos reemplazando al grupo por el semáforo. ¡Vamos a mirar!

 NSUInteger numKeys = [keys count]; // set the count of the semaphore to the number of times it must be signalled before // being exhausted. Up to `numKeys` waits will actually wait for signals this way. // Additional waits will return immediately. dispatch_semaphore_t sem = dispatch_semaphore_create(numKeys); for (int i = 0; i < numKeys; i++) { dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (NSUInteger i = 0; i < numKeys; i++) { __block NSString *key = [keys objectAtIndex:i]; dispatch_async(queue, ^{ [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){ ...; // This decrements the semaphore's count by one. The calling code will be // woken up by this, and will then wait again until no blocks remain to wait for. dispatch_semaphore_signal(sem); }]; }); } // At this point, all the work is running (or could have already completed, who knows?). // We don't want this function to continue running until we know all of the blocks // have run, so we wait on our semaphore a number of times equalling the number // of signals we expect to get. If all the blocks have run to completion before we've // waited for all of them, the additional waits will return immediately. for (int i = 0; i < numKeys; i++) { dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } // Now all the tasks should have completed. dispatch_release(sem); 

Sí, como se ha indicado, dispatch_group_wait() no está esperando porque postImage:completionHandler: llamada parece ser asincrónica. Y si REALMENTE necesita este bloque de código para ejecutarse sincrónicamente, un semáforo o locking parecería ser una solución adecuada.

Si, sin embargo, solo desea recostackr todas las respuestas a un dictionary para su procesamiento, creo que la solución más adecuada es utilizar GCD en toda su extensión. Y usar una queue de despacho para administrar el dictionary mutable; Esta parece ser la solución preferida por Apple en la mayoría de los documentos que he visto.

El quid de la solución es transferir esencialmente la propiedad del dictionary mutable a una sola queue y luego solo modificarla desde esa queue. La 'propiedad' a la que me refiero no es la propiedad del object en el sentido de gestión de la memory, sino la propiedad del derecho a modificar el sentido.

Yo consideraría hacer algo como esto:

 NSArray *keys = [images allKeys]; // We will not be reasigning the 'responses' pointer just sending messages to the NSMutableDictionary object __block is not needed. NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]]; // Create a queue to handle messages to the responses dictionary since mutables are not thread safe. // Consider this thread the owner of the dictionary. dispatch_queue_t responsesCallbackQueue = dispatch_queue_create("com.mydomain.queue", DISPATCH_QUEUE_SERIAL); for (NSString *key in keys) { // This async call may not be needed since postImage:completionHandler: seems to be an async call itself. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){ dispatch_async(responsesCallbackQueue, ^{ [responses setObject:response forKey:key]; // Perhaps log success for individual requests. if (responses.count == keys.count){ NSLog(@"All requests have completed"); // All responses are available to you here. // You can now either transfer back 'ownership' of the dictionary. dispatch_async(dispatch_get_main_queue(), ^{ [self requestsHaveFinished:responses]; }); } }); }]; }); }