2012-04-09 10 views
6

Tengo una aplicación Cocoa (Mac OS X SDK 10.7) que está realizando algunos procesos a través de Grand Central Dispatch (GCD). Estos procesos están manipulando algunos datos básicos NSManagedObjects (no basados ​​en documentos) de una manera que creo que es segura para subprocesos (creando un nuevo managedObjectContext para usar en este hilo).Terminación agraciada de NSApplication con Core Data y Grand Central Dispatch (GCD)

El problema que tengo es cuando el usuario intenta salir de la aplicación mientras la cola de distribución aún se está ejecutando.

Se llama al delegado de NSApplication antes de salir realmente.

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 

Aparece el error "No se pudieron combinar los cambios". Lo cual es algo esperado ya que todavía hay operaciones que se realizan a través de los diferentes managedObjectContext. Luego me presentan el NSAlert de la plantilla que se genera con una aplicación de datos básicos.

En el Threading Programming Guide hay una sección llamada "Tenga cuidado con los comportamientos de subprocesos en el tiempo de salida" que alude al método replyToApplicationShouldTerminate:. Estoy teniendo un pequeño problema para implementar esto.

Lo que me gustaría es que mi aplicación complete el procesamiento de los artículos en cola y luego termine sin presentar un mensaje de error al usuario. También sería útil actualizar la vista o usar una hoja para que el usuario sepa que la aplicación está realizando alguna acción y finalizará cuando se complete la acción.

¿Dónde y cómo implementaría este comportamiento?

Solución: Así que tuve algunos problemas diferentes aquí.

  1. tuve bloques que se accede a los datos fundamentales en un dispatch_queue prevención de mi solicitud de terminación de gracia.

  2. Cuando traté de agregar un nuevo artículo a dispatch_queue se inició una nueva instancia de dispatch_queue en un nuevo hilo.

Lo que hice para solucionar este fue el uso NSNotificationCenter en mi AppDelegate (donde estaba siendo llamado (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender En el código de la plantilla que Core de datos genera agregar lo siguiente:.

// Customize this code block to include application-specific recovery steps. 
if (error) { 
    // Do something here to add queue item in AppController 
    [[NSNotificationCenter defaultCenter] postNotificationName:@"TerminateApplicationFromQueue" object:self]; 
    return NSTerminateLater; 
} 

Luego, en AppController añadir una observador de la notificación (he añadido esto a awakeFromNib):

- (void)awakeFromNib { 
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 
    [center addObserver:self selector:@selector(terminateApplicationFromQueue:) name:@"TerminateApplicationFromQueue" object:nil]; 

    // Set initial state of struct that dispatch_queue checks to see if it should terminate the application. 
    appTerminating.isAppTerminating = NO; 
    appTerminating.isTerminatingNow = NO; 
} 

también he creado un struct que se puede verificar para ver si el usuario desea finalizar la aplicación. (Establecí el estado inicial de la estructura en awakeFromNib arriba). Coloque el struct después de sus declaraciones @synthesize:

struct { 
    bool isAppTerminating; 
    bool isTerminatingNow; 
} appTerminating; 

ya por la prolongada dispatch_queue que impide la aplicación de la terminación de gracia.Cuando inicialmente creo este dispatch_queue, se usa un ciclo for para agregar los elementos que necesitan actualización. Después de este bucle se ejecuta, he clavado en otro elemento de cola que se compruebe la struct para ver si la aplicación debe terminar:

// Additional queue item block to check if app should terminate and then update struct to terminate if required. 
dispatch_group_async(refreshGroup, trackingQueue, ^{ 
    NSLog(@"check if app should terminate"); 
    if (appTerminating.isAppTerminating) { 
     NSLog(@"app is terminating"); 
     appTerminating.isTerminatingNow = YES; 
    } 
}); 
dispatch_release(refreshGroup); 

Y el método que se llamará cuando se recibe la notificación:

- (void)terminateApplicationFromQueue:(NSNotification *)notification { 
    // Struct to check against at end of dispatch_queue to see if it should shutdown. 
    if (!appTerminating.isAppTerminating) { 
     appTerminating.isAppTerminating = YES; 
     dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL 
     dispatch_group_t terminateGroup = dispatch_group_create(); 

     dispatch_group_async(terminateGroup, terminateQueue, ^{ 
      NSLog(@"termination queued until after operation is complete"); 
      while (!appTerminating.isTerminatingNow) { 
      // add a little delay before checking termination status again 
       [NSThread sleepForTimeInterval:0.5]; 
      } 
      NSLog(@"terminate now"); 
      [NSApp replyToApplicationShouldTerminate:YES]; 
     }); 
     dispatch_release(terminateGroup); 
    } 
} 
+0

Solo para aclarar: ¿estás regresando? 'NSTerminateLater' de' applicationShouldTerminate: ', como se menciona en' replyToApplicationShouldTerminate: 'docs? –

+0

No, yo no. No estaba seguro de dónde agregar esto. –

+0

¿Qué devuelve, entonces? –

Respuesta

0

no he tratado con este mismo, pero sólo de mi lectura de los documentos, parece que lo que se debe hacer es:

  1. Volver NSTerminateLater de applicationShouldTerminate:. Esto le permite al sistema saber que su aplicación aún no está lista para terminar ,, pero lo hará en breve.
  2. Encola un bloque "final" en tu cola de despacho. (Debe asegurarse de que los otros bloques no se pongan en cola después de esto. Este bloque se ejecutará después de que se haya realizado todo el trabajo. Tenga en cuenta que la cola debe ser en serie, no una de las colas concurrentes) para que esto funcione correctamente. .) El bloque "final" debe hacer [NSApp replyToApplicationShouldTerminate:YES];, que completará el proceso de terminación normal.

No hay una forma directa de averiguar si una cola GCD sigue funcionando. La única otra cosa que puedes hacer (que yo sepa) para manejar esto es poner todos los bloques en un dispatch group, y luego esperar en el grupo en applicationShouldTerminate: (usando dispatch_group_wait().

+0

Parece que está a medio camino de allí. Por algún motivo, dispatch_queue se ejecuta simultáneamente en lugar de serialmente. He intentado crear mi cola como 'trackingQueue = dispatch_queue_create ("com.example.addtracking", NULL);' y 'trackingQueue = dispatch_queue_create ("com.example.addtracking", DISPATCH_QUEUE_SERIAL); ' también estoy creando un dispatch_group: ' refreshGroup = dispatch_group_create(); se están agregando ' elementos al grupo de despacho: ' dispatch_group_async (refreshGroup, trackingQueue,^{ NSLog (@ "algún tipo de acción va aquí "); });' –

+0

Esperando en el grupo debería funcionar si la cola es serial o concurrente (la serial también es la predeterminada). ¿Qué te hace pensar que es concurrente? ¿Estás seguro de que nada más se pone en cola una vez que presionas 'applicationShouldTerminate:'? –

+0

Tengo algunas cosas de depuración que inician sesión en la consola de cada bloque para ver qué sucede. Cuando agrego el bloque de terminación a la cola, ese bloque se ejecuta antes de que se hayan ejecutado otros elementos en la cola. La aplicación ahora parece estar terminando sin error, pero no quiero enviar una actualización antes de estar seguro de que todo está funcionando como se esperaba. En cuanto a los elementos que se agregan a la cola después de que he enviado la 'applicationShouldTerminate', no he creado un mecanismo para evitar que eso suceda. Creo que podría crear una estructura para manejar eso. –

Cuestiones relacionadas