2009-10-29 16 views
5

ho un NSOperationQueue che contiene 2 NSOperations ed è impostato per eseguire una dopo l'altra modificando setMaxConcurrentOperationCount a 1.Problemi Queuing simultanea e non simultanea

Una delle operazioni è un'operazione standard non simultanea (solo un metodo main) che recupera in modo sincrono alcuni dati dal web (ovviamente sul thread dell'operazione separata). L'altra operazione è un'operazione simultanea in quanto è necessario utilizzare un codice che deve essere eseguito in modo asincrono.

Il problema è che ho scoperto che l'operazione simultanea funziona solo se prima viene aggiunta alla coda. Se viene dopo qualsiasi operazione non simultanea, allora il metodo start viene chiamato correttamente, ma dopo che il metodo termina e ho impostato la mia connessione a un metodo callback, non lo fa mai. Nessuna ulteriore operazione nella coda viene eseguita dopo. È come se si blocca dopo il ritorno del metodo di avvio, e non vengono richiamati i callback da qualsiasi connessione url!

Se la mia operazione simultanea viene inserita per prima nella coda, tutto funziona correttamente, i callback asincroni funzionano e l'operazione successiva viene eseguita dopo che è stata completata. Non capisco affatto!

È possibile vedere il codice di prova per la mia NSOperation concomitante di seguito, e sono abbastanza sicuro che sia solido.

Qualsiasi aiuto sarebbe molto apprezzato!

principale Osservazione Discussione:

ho appena scoperto che se il funzionamento in parallelo è il primo nella coda quindi il metodo [start] viene chiamato sul thread principale. Tuttavia, se non è il primo sulla coda (se è dopo un concorrente o non simultaneo), il metodo [start] non viene chiamato sul thread principale. Questo sembra importante in quanto si adatta allo schema del mio problema. Quale potrebbe essere la ragione di questo?

Concurrent Codice 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 

Queuing Codice

// 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]; 

risposta

10

Ho scoperto qual era il problema!

Questi due inestimabili articoli di Dave Dribin descrivono operazioni simultanee in grande dettaglio, così come i problemi che Snow Leopard & l'SDK iPhone introducono quando si chiama le cose in modo asincrono che richiedono un ciclo di esecuzione.

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/

Grazie a Chris Suter troppo per avermi nella giusta direzione!

Il punto cruciale di esso è quello di garantire che il metodo start ci ha chiamato sul thread principale:

- (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

anche io ho lo stesso problema. Ma nel mio caso, il metodo start non viene anche chiamato per le operazioni appena aggiunte dopo qualche volta. La coda mostra ancora lo stato 'In esecuzione'. Quindi il metodo sopra non può essere usato. Conosci qualche altra soluzione? Per favore aiuto.. –

0

Non ho notato, e non vedo alcuna menzione di addDependency:, che sembrerebbe come un prerequisito per ottenere operazioni da eseguire nell'ordine corretto.

In breve, la seconda operazione dipende dal primo.

+0

Oh sì ho dimenticato di dire sul codice coda. Non ho aggiunto alcuna dipendenza, ma non per nessuna ragione. Tutte le operazioni non sono correlate, ma voglio solo che vengano eseguite una dopo l'altra. Le dipendenze farebbero qualche differenza per il problema che sto avendo? Ho impostato setMaxConcurrentOperationCount su 1 e ho pensato che sarebbe stato sufficiente. Grazie per la risposta! –

+0

Ho appena aggiunto delle dipendenze e non ha avuto alcun effetto sul mio problema. –

6

Il problema è molto probabile con NSURLConnection. NSURLConnection dipende da un ciclo di esecuzione che esegue una determinata modalità (di solito solo quelle predefinite).

Ci sono una serie di soluzioni al problema:

  1. Assicurarsi che questa operazione viene eseguito solo sul thread principale. Se lo facessi su OS X, dovresti controllare che faccia quello che vuoi in tutte le modalità di esecuzione (ad esempio modal e modalità di tracciamento degli eventi), ma non so quale sia l'accordo sull'iPhone.

  2. Creare e gestire il proprio thread. Non è una bella soluzione.

  3. Chiama - [NSURLConnection scheduleInRunLoop: forMode:] e passa il thread principale o un altro thread che conosci. Se lo fai, probabilmente vorrai chiamare [NSURLConnection unscheduleInRunLoop: forMode:] prima altrimenti potresti ricevere i dati in più thread (o almeno questo è quello che la documentazione sembra suggerire).

  4. Utilizzare qualcosa come + [NSData dataWithContentsOfURL: opzioni: errore:] e che semplificherà anche l'operazione poiché è possibile renderlo un'operazione non simultanea.

  5. Variante su # 4: utilizzare + [NSURLConnection sendSynchronousRequest: returnResponse: error:].

Se si riesce a farla franca, do # 4 o 5 #.

+0

Grazie per la risposta. Il codice che ho postato è solo un semplice esempio che ho creato a scopo di test. Il programma attuale utilizza un'API che è al di fuori del mio controllo ed è di natura asincrona. Tuttavia mi hai indotto a rileggere correttamente 2 articoli interessanti che hanno risolto il problema! Pubblicherò una risposta in modo che sia chiaro quando gli altri hanno il problema, ma grazie per aver dedicato del tempo e sei morto sul loop di esecuzione! –