objective-c cocoa core-data grand-central-dispatch objective-c-blocks

objective c - dispatch_sync vs. dispatch_async en cola principal



objective-c cocoa (3)

Tenga paciencia conmigo, esto va a tomar un poco de explicación. Tengo una función que se parece a la siguiente.

Contexto: "aProject" es una entidad de datos básicos llamada LPProject con una matriz llamada ''memberFiles'' que contiene instancias de otra entidad de datos centrales llamada LPFile. Cada LPFile representa un archivo en el disco y lo que queremos hacer es abrir cada uno de esos archivos y analizar su texto, buscando las declaraciones @import que apuntan a OTROS archivos. Si encontramos las declaraciones @import, queremos ubicar el archivo al que apuntan y luego ''enlazar'' ese archivo a este agregando una relación a la entidad de datos central que representa el primer archivo. Dado que todo eso puede llevar tiempo en archivos grandes, lo haremos fuera del hilo principal usando GCD.

- (void) establishImportLinksForFilesInProject:(LPProject *)aProject { dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (LPFile *fileToCheck in aProject.memberFiles) { if (//Some condition is met) { dispatch_async(taskQ, ^{ // Here, we do the scanning for @import statements. // When we find a valid one, we put the whole path to the imported file into an array called ''verifiedImports''. // go back to the main thread and update the model (Core Data is not thread-safe.) dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"Got to main thread."); for (NSString *import in verifiedImports) { // Add the relationship to Core Data LPFile entity. } });//end block });//end block } } }

Ahora, aquí es donde las cosas se ponen raras:

Este código funciona, pero veo un problema extraño. Si lo ejecuto en un LPProject que tiene algunos archivos (alrededor de 20), se ejecuta perfectamente. Sin embargo, si lo ejecuto en un LPProject que tiene más archivos (digamos, 60-70), NO se ejecuta correctamente. Nunca volvemos al hilo principal, el NSLog(@"got to main thread"); nunca aparece y la aplicación se cuelga. PERO (y aquí es donde las cosas se ponen realmente raras) --- si ejecuto el código en el proyecto pequeño PRIMERO y luego lo ejecuto en el proyecto grande, todo funciona perfectamente. SOLAMENTE cuando ejecuto el código en el proyecto grande primero aparece el problema.

Y aquí está el truco, si cambio la segunda línea de envío a esto:

dispatch_async(dispatch_get_main_queue(), ^{

(Es decir, use async lugar de sync para enviar el bloque a la cola principal), todo funciona todo el tiempo. Perfectamente. ¡Independientemente de la cantidad de archivos en un proyecto!

No puedo explicar este comportamiento. Cualquier ayuda o consejo sobre qué probar a continuación sería de agradecer.


Creo que Ryan está en el camino correcto: simplemente hay demasiados hilos que se generan cuando un proyecto tiene 1.500 archivos (la cantidad con la que decidí probar).

Por lo tanto, refactoré el código anterior para que funcione así:

- (void) establishImportLinksForFilesInProject:(LPProject *)aProject { dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(taskQ, ^{ // Create a new Core Data Context on this thread using the same persistent data store // as the main thread. Pass the objectID of aProject to access the managedObject // for that project on this thread''s context: NSManagedObjectID *projectID = [aProject objectID]; for (LPFile *fileToCheck in [backgroundContext objectWithID:projectID] memberFiles]) { if (//Some condition is met) { // Here, we do the scanning for @import statements. // When we find a valid one, we put the whole path to the // imported file into an array called ''verifiedImports''. // Pass this ID to main thread in dispatch call below to access the same // file in the main thread''s context NSManagedObjectID *fileID = [fileToCheck objectID]; // go back to the main thread and update the model // (Core Data is not thread-safe.) dispatch_async(dispatch_get_main_queue(), ^{ for (NSString *import in verifiedImports) { LPFile *targetFile = [mainContext objectWithID:fileID]; // Add the relationship to targetFile. } });//end block } } // Easy way to tell when we''re done processing all files. // Could add a dispatch_async(main_queue) call here to do something like UI updates, etc });//end block }

Entonces, básicamente, ahora estamos generando un hilo que lee todos los archivos en lugar de un hilo por archivo. Además, resulta que llamar a dispatch_async () en main_queue es el enfoque correcto: el subproceso de trabajo enviará ese bloque al subproceso principal y NO esperará a que vuelva antes de proceder a escanear el siguiente archivo.

Esta implementación esencialmente configura una cola "serial" como sugirió Ryan (el bucle for es la parte serial de ella), pero con una ventaja: cuando termina el bucle for, terminamos de procesar todos los archivos y podemos simplemente poner un dispatch_async (main_queue) bloquea para hacer lo que quieras. Es una muy buena forma de saber cuándo finalizó la tarea de procesamiento concurrente y que no existía en mi versión anterior.

La desventaja aquí es que es un poco más complicado trabajar con Core Data en múltiples hilos. Pero este enfoque parece ser a prueba de balas para proyectos con 5.000 archivos (que es el más alto que he probado).


Creo que es más fácil de entender con el diagrama:

Para la situación descrita por el autor:

| taskQ | *********** inicio |

| dispatch_1 *********** | ---------

| dispatch_2 ************* | ---------

.

| dispatch_n *************************** | ----------

| cola principal (sincronización) | ** empezar a enviar a main |

************************* | --dispatch_1-- | --dispatch_2-- | --dispatch3-- | ****** *********************** | --dispatch_n |,

que hacen que la cola principal de sincronización esté tan ocupada que finalmente falla la tarea.


Este es un problema común relacionado con E / S de disco y GCD. Básicamente, GCD probablemente está generando un hilo para cada archivo, y en cierto punto tienes demasiados hilos para que el sistema lo haga en un tiempo razonable.

Cada vez que llamas a dispatch_async () y en ese bloque intentas realizar una E / S (por ejemplo, parece que estás leyendo algunos archivos aquí), es probable que el hilo en el que se está ejecutando ese bloque de código bloqueará (el SO lo detiene) mientras espera que los datos se lean del sistema de archivos. La forma en que GCD funciona es tal que cuando ve que uno de sus subprocesos de trabajo está bloqueado en E / S y todavía le está pidiendo que haga más trabajo al mismo tiempo, generará un nuevo hilo de trabajo. Por lo tanto, si intenta abrir 50 archivos en una cola simultánea, es probable que termine provocando que GCD engendre ~ 50 hilos.

Esto es demasiados hilos para que el sistema tenga un servicio significativo, y terminas muriendo de hambre con tu hilo principal para CPU.

La forma de solucionar esto es utilizar una cola en serie en lugar de una cola simultánea para realizar sus operaciones basadas en archivos. Es facil de hacer. Deberá crear una cola en serie y almacenarla como un ivar en su objeto para que no termine creando múltiples colas en serie. Por lo tanto, elimine esta llamada:

dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Agregue esto en su método init:

taskQ = dispatch_queue_create("com.yourcompany.yourMeaningfulLabel", DISPATCH_QUEUE_SERIAL);

Agregue esto en su método dealloc:

dispatch_release(taskQ);

Y agregue esto como un ivar en su declaración de clase:

dispatch_queue_t taskQ;