tutorials tutorial example objective-c cocoa multithreading grand-central-dispatch

objective-c - tutorial - nsoperationqueue swift 3 example



Colas de envío: cómo saber si se están ejecutando y cómo detenerlas (3)

Solo estoy jugando con GCD y he escrito una aplicación CoinFlipper de juguete.

Este es el método que arroja las monedas:

- (void)flipCoins:(NSUInteger)nFlips{ // Create the queues for work dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL); // Split the number of flips into whole chunks of kChunkSize and the remainder. NSUInteger numberOfWholeChunks = nFlips / kChunkSize; NSUInteger numberOfRemainingFlips = nFlips - numberOfWholeChunks * kChunkSize; if (numberOfWholeChunks > 0) { for (NSUInteger index = 0; index < numberOfWholeChunks; index++) { dispatch_async(queue, ^{ NSUInteger h = 0; NSUInteger t = 0; flipTheCoins(kChunkSize, &h, &t); dispatch_async(mainQueue, ^{ self.nHeads += h; self.nTails += t; }); }); } } if (numberOfRemainingFlips > 0) { dispatch_async(queue, ^{ NSUInteger h = 0; NSUInteger t = 0; flipTheCoins(numberOfRemainingFlips, &h, &t); dispatch_async(mainQueue, ^{ self.nHeads += h; self.nTails += t; }); }); } }

Como puedes ver; Estoy dividiendo el número de volteretas en trozos grandes volteándolos en segundo plano y actualizando propiedades en la cola principal. Las propiedades están siendo observadas por el controlador de ventana y una UI se actualiza con los resultados de ejecución.

He consultado la Guía de programación de simultaneidad y los documentos de GCD, y aunque hay una forma de suspender una cola, no hay forma de detenerlos y eliminar todos los objetos en cola y no en ejecución.

Me gustaría poder conectar un botón "detener" para cancelar el volteo una vez que haya comenzado. Con NSOperationQueue puedo observar la propiedad operationCount para saber si se está ejecutando y cancelAllOperations para eliminar los bloques en cola.

He consultado la Guía de programación de simultaneidad y los documentos de GCD, y aunque hay una forma de suspender una cola, no hay forma de detenerlos y eliminar todos los objetos en cola y no en ejecución.

Asi que :-

  1. ¿Cómo puedo saber si los bloques que he agregado a una cola todavía están esperando?
  2. ¿Cómo cancelo bloques que aún no se han ejecutado?
  3. Soy nuevo en GCD, así que lo estoy haciendo bien?

Cómo saber si se está ejecutando

BOOL dispatch_queue_is_empty(dispatch_queue_t queue) { dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); dispatch_async(queue, ^{ dispatch_group_leave(group); }); int64_t maxWaitTime = 0.00000005 * NSEC_PER_SEC; BOOL isReady = dispatch_group_wait(group, maxWaitTime) == 0; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); }); return isReady; }

Para probar en la aplicación

dispatch_queue_t queue = dispatch_queue_create("test", 0); NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO"); dispatch_async(queue, ^{ for(int i = 0; i < 100; i++) { NSLog(@"... %i", i); } }); NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");

Resultado

Is empty YES Is empty NO ... 0 ... 1 ... 2 ... 3 ... 4 ... 5 ... 6 ... 7 ... 8 ... 9

El valor predeterminado para la variable maxWaitTime puede maxWaitTime al resultado deseado.


Esta es una pregunta semi común cuando se programa con GCD.

La respuesta corta es que GCD no tiene una API de cancelación para las colas. Lo racional:

  1. la gestión de la memoria se haría mucho más complicada, porque un bloque dado podría ser responsable de liberar una asignación de memoria determinada. Al ejecutar siempre el bloque, GCD garantiza que la gestión de la memoria siga siendo sencilla.
  2. Es prácticamente imposible detener un bloque en ejecución sin corromper el estado.
  3. La mayoría del código que necesita lógica de cancelación ya está rastreando ese estado en estructuras de datos privadas.

En todos estos casos, es mucho más eficiente y poderoso escribir código como este:

dispatch_async(my_obj->queue, ^{ bool done = false; // do_full_update() takes too long, therefore: while ( !my_obj->cancelled && !done ) { done = do_partial_update(my_obj); } });

Ah, y para saber si una cola ha terminado de ejecutar todos los bloques en cola, su código puede simplemente ejecutar un bloque vacío con la API síncrona :

dispatch_sync(my_obj->queue, ^{});

Como se mencionó en los comentarios, una mejor forma de saber cuándo se realiza su trabajo es usar grupos de despacho. Envíe todos sus bloques al grupo y luego puede agregar un controlador de finalización al grupo. Una vez que el trabajo esté completo, se ejecutará el bloque de finalización.

dispatch_group_t myGroup = dispatch_group_create(); dispatch_group_async(myGroup, my_obj->queue, ^{ bool done = false; while ( !my_obj->cancelled && !done ) { done = do_partial_update(my_obj); } }); dispatch_group_notify(myGroup, my_obj->queue, ^{ NSLog(@"Work is done!"); dispatch_release(myGroup); });

Una vez que todos sus bloques se hayan completado, el grupo estará vacío y activará el bloque de notificación. Desde allí, puede actualizar la interfaz de usuario, etc.

¡Buena suerte y diviertete!


Si tiene una cola de despacho en serie O una cola de despacho concurrente, aquí hay un código que puede hacer lo mismo.

BOOL __block queueIsEmpty = false; dispatch_barrier_async (_dispatchQueue, ^{ queueIsEmpty = true; }); while (!queueIsEmpty) { int i = 0; // NOOP instruction } // At this point your queue should be empty.