2009-10-29 13 views
5

tengo una NSOperationQueue que contiene 2 NSOperations y que lleve a cabo uno tras otro por el ajuste setMaxConcurrentOperationCount a 1.Problemas Queuing concurrente y no concurrentes

Una de las operaciones es una operación estándar no concurrente (solo un método main) que recupera de forma sincrónica algunos datos de la web (en el hilo de operación separado por supuesto). La otra operación es una operación simultánea ya que necesito usar algún código que se debe ejecutar de forma asíncrona.

El problema es que he descubierto que la operación concurrente solo funciona si se agrega primero a la cola. Si se produce después de cualquier operación no concurrente, entonces extrañamente se llama bien al método start, pero después de que el método finaliza y configuro mi conexión para volver a llamar a un método, nunca lo hace. No se ejecutan más operaciones en la cola después. Es como si se cuelga después de que regrese el método de inicio, ¡y no se llama a ninguna devolución de llamada desde ninguna conexión de url!

Si mi operación simultánea se pone primero en la cola, todo funciona bien, las devoluciones de llamada asíncronas funcionan y la operación subsiguiente se ejecuta después de que se haya completado. ¡No entiendo para nada!

Puede ver el código de prueba para mi NSOperation concurrente a continuación, y estoy bastante seguro de que es sólido.

¡Cualquier ayuda sería muy apreciada!

principal Observación Tema:

acabo de descubrir que si el funcionamiento en paralelo es el primero en la cola entonces el método [start] se llama en el hilo principal. Sin embargo, si no está primero en la cola (si está después de concurrente o no concurrente), entonces no se llama al método [start] en el hilo principal. Esto parece importante ya que se ajusta al patrón de mi problema. ¿Cuál podría ser la razón de ésto?

concurrente Código NSOperation:

@interface ConcurrentOperation : NSOperation { 
    BOOL executing; 
    BOOL finished; 
} 
- (void)beginOperation; 
- (void)completeOperation; 
@end 

@implementation ConcurrentOperation 
- (void)beginOperation { 
    @try { 

     // Test async request 
     NSURLRequest *r = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.google.com"]]; 
     NSURLConnection *c = [[NSURLConnection alloc] initWithRequest:r delegate:self]; 
     [r release]; 

    } @catch(NSException * e) { 
     // Do not rethrow exceptions. 
    } 
} 
- (void)connectionDidFinishLoading:(NSURLConnection *)connection { 
    NSLog(@"Finished loading... %@", connection); 
    [self completeOperation]; 
} 
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 
    NSLog(@"Finished with error... %@", error); 
    [self completeOperation]; 
} 
- (void)dealloc { 
    [super dealloc]; 
} 
- (id)init { 
    if (self = [super init]) { 

     // Set Flags 
     executing = NO; 
     finished = NO; 

    } 
    return self; 
} 
- (void)start { 

    // Main thread? This seems to be an important point 
    NSLog(@"%@ on main thread", ([NSThread isMainThread] ? @"Is" : @"Not")); 

    // Check for cancellation 
    if ([self isCancelled]) { 
     [self completeOperation]; 
     return; 
    } 

    // Executing 
    [self willChangeValueForKey:@"isExecuting"]; 
    executing = YES; 
    [self didChangeValueForKey:@"isExecuting"]; 

    // Begin 
    [self beginOperation]; 

} 

// Complete Operation and Mark as Finished 
- (void)completeOperation { 
    BOOL oldExecuting = executing; 
    BOOL oldFinished = finished; 
    if (oldExecuting) [self willChangeValueForKey:@"isExecuting"]; 
    if (!oldFinished) [self willChangeValueForKey:@"isFinished"]; 
    executing = NO; 
    finished = YES; 
    if (oldExecuting) [self didChangeValueForKey:@"isExecuting"]; 
    if (!oldFinished) [self didChangeValueForKey:@"isFinished"]; 
} 

// Operation State 
- (BOOL)isConcurrent { return YES; } 
- (BOOL)isExecuting { return executing; } 
- (BOOL)isFinished { return finished; } 

@end 

Código Queuing

// Setup Queue 
myQueue = [[NSOperationQueue alloc] init]; 
[myQueue setMaxConcurrentOperationCount:1]; 

// Non Concurrent Op 
NonConcurrentOperation *op1 = [[NonConcurrentOperation alloc] init]; 
[myQueue addOperation:op1]; 
[op1 release]; 

// Concurrent Op 
ConcurrentOperation *op2 = [[ConcurrentOperation alloc] init]; 
[myQueue addOperation:op2]; 
[op2 release]; 

Respuesta

10

¡He descubierto cuál era el problema!

Estos dos invaluables artículos de Dave Dribin describen las operaciones simultáneas en gran detalle, así como los problemas que Snow Leopard & el iPhone SDK introducen al llamar a las cosas de forma asíncrona que requieren un bucle de ejecución.

http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/ http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/

Gracias a Chris Suter demasiado para mí apuntando en la dirección correcta!

El quid de la cuestión es asegurarse de que el método start nosotros llamamos en el hilo principal:

- (void)start { 

    if (![NSThread isMainThread]) { // Dave Dribin is a legend! 
     [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; 
     return; 
    } 

    [self willChangeValueForKey:@"isExecuting"]; 
    _isExecuting = YES; 
    [self didChangeValueForKey:@"isExecuting"]; 

    // Start asynchronous API 

} 
+0

También tengo este mismo problema. Pero en mi caso, el método de inicio también no requiere nuevas operaciones agregadas después de algunas veces. Cola todavía muestra el estado 'En ejecución'. Entonces el método anterior no puede ser usado. ¿Conoces alguna otra solución? Por favor ayuda.. –

0

No me di cuenta, ni veo ninguna mención de addDependency:, lo que parece como un requisito previo para hacer que las operaciones se ejecuten en el orden correcto

En resumen, la segunda operación depende de la primera.

+0

Oh, sí me olvidó mencionar sobre el código de cola. No he agregado ninguna dependencia, pero no por ninguna razón realmente. Todas las operaciones no están relacionadas, pero solo quiero que se ejecuten una tras otra. Las dependencias, ¿harían alguna diferencia al problema que estoy teniendo? Configuré setMaxConcurrentOperationCount en 1 y pensé que sería suficiente. ¡Gracias por su respuesta! –

+0

Acabo de agregar dependencias y no ha tenido ningún efecto en mi problema. –

6

Su problema es muy probable con NSURLConnection. NSURLConnection depende de un ciclo de ejecución que ejecute un determinado modo (generalmente solo los predeterminados).

Hay una serie de soluciones a su problema:

  1. Asegúrese de que esta operación sólo se ejecuta en el hilo principal. Si estuvieras haciendo esto en OS X, querrás comprobar que hace lo que quieres en todos los modos de ciclo de ejecución (por ejemplo modos de seguimiento modal y de eventos), pero no sé cuál es el problema en el iPhone.

  2. Crea y administra tu propio hilo. No es una buena solución.

  3. Llamar - [NSURLConnection scheduleInRunLoop: forMode:] y pasar el hilo principal u otro hilo que conozca. Si hace esto, probablemente quiera llamar a [NSURLConnection unscheduleInRunLoop: forMode:] primero; de lo contrario, podría estar recibiendo los datos en múltiples hilos (o al menos eso es lo que la documentación parece sugerir).

  4. Use algo como + [NSData dataWithContentsOfURL: options: error:] y eso también simplificará su operación ya que puede hacer que sea una operación no concurrente en su lugar.

  5. Variante en # 4: use + [NSURLConnection sendSynchronousRequest: returningResponse: error:].

Si puede salirse con la suya, haga n. ° 4 o n. ° 5.

+0

Gracias por su respuesta. El código que publiqué es solo un ejemplo simple que creé para fines de prueba. El programa real usa una API que está fuera de mi control y es de naturaleza asincrónica. Sin embargo, me condujo a volver a leer correctamente 2 artículos interesantes que han resuelto el problema. Voy a publicar una respuesta para que quede claro cuando otros tienen el problema, pero gracias por tomarse el tiempo y estás completamente equivocado sobre los bucles de ejecución. –