5

j'ai un NSOperationQueue qui contient 2 NSOperations et est placé pour les exécuter l'une après l'autre par la mise setMaxConcurrentOperationCount à 1.Problèmes simultanés et NSOperations Queueing non simultanées

L'une des opérations est une norme non-fonctionnement simultané (juste une méthode main) qui récupère de manière synchrone certaines données du web (sur le fil d'opération séparé bien sûr). L'autre opération est une opération simultanée car j'ai besoin d'utiliser du code qui doit s'exécuter de manière asynchrone.

Le problème est que j'ai découvert que l'opération simultanée ne fonctionne que si elle est d'abord ajoutée à la file d'attente. Si elle vient après des opérations non-concurrentes, alors étrangement la méthode start est appelée bien, mais après que cette méthode se termine et que j'ai configuré ma connexion à callback une méthode, elle ne le fait jamais. Aucune autre opération dans la file d'attente n'est exécutée après. C'est comme si elle se bloque après le retour de la méthode start, et aucun callback de n'importe quelle connexion d'URL n'est appelé!

Si mon opération simultanée est placée en premier dans la file d'attente, tout fonctionne correctement, les callbacks asynchrones fonctionnent et l'opération suivante est exécutée une fois l'opération terminée. Je ne comprends pas du tout!

Vous pouvez voir ci-dessous le code de test de mon NSOperation simultanée, et je suis presque certain qu'il est solide.

Toute aide serait grandement appréciée!

Discussion principale Observation:

Je viens de découvrir que si l'opération simultanée est d'abord sur la file d'attente puis la méthode [start] est appelée sur le thread principal. Cependant, s'il n'est pas le premier dans la file d'attente (s'il est après un concurrent ou un non-concurrent), la méthode [start] n'est pas appelée sur le thread principal. Cela semble important car il correspond au modèle de mon problème. Quelle pourrait en être la raison?

simultanées NSOperation code:

@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 

code Queueing

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

Répondre

10

J'ai découvert quel était le problème!

Ces deux précieux articles de Dave Dribin décrivent des opérations simultanées en détail, ainsi que les problèmes que Snow Leopard & l'iPhone SDK lorsque vous appelez les choses introduisent de manière asynchrone qui nécessitent une boucle d'exécution.

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/

Merci à Chris Suter aussi pour moi pointant dans la bonne direction!

Le point crucial de c'est de faire en sorte que la méthode start nous appelions sur le thread 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

J'ai aussi eu ce même problème. Mais dans mon cas, la méthode de démarrage ne demande pas non plus de nouvelles opérations après parfois. La file d'attente montre toujours le statut 'Running'. Donc, la méthode ci-dessus ne peut pas être utilisée. Connaissez-vous une autre solution? S'il vous plaît aider .. –

0

Je n'ai pas remarqué, et je ne vois aucune mention de addDependency:, qui semble comme une condition préalable à obtenir des opérations à exécuter dans le bon ordre.

En résumé, la deuxième opération dépend de la première.

+0

Oh oui j'oublié de mentionner au sujet du code de file d'attente. Je n'ai ajouté aucune dépendance, mais pas pour une raison quelconque. Toutes les opérations ne sont pas liées, mais je veux juste qu'elles courent l'une après l'autre. Est-ce que les dépendances feraient une différence par rapport au problème que j'ai? J'ai mis le setMaxConcurrentOperationCount à 1 et j'ai pensé que ce serait suffisant. Merci pour votre réponse! –

+0

Je viens d'ajouter des dépendances et cela n'a eu aucun effet sur mon problème. –

6

Votre problème est probablement lié à NSURLConnection. NSURLConnection dépend d'une boucle d'exécution exécutant un certain mode (en général, ceux par défaut).

Il y a un certain nombre de solutions à votre problème:

  1. Assurez-vous que cette opération ne fonctionne que sur le thread principal. Si vous faisiez cela sur OS X, vous voudriez vérifier qu'il fait ce que vous voulez dans tous les modes de boucle d'exécution (par exemple modes de suivi modal et d'événement), mais je ne sais pas quel est le problème sur l'iPhone.

  2. Créez et gérez votre propre thread. Pas une bonne solution.Appelez - [NSURLConnection scheduleInRunLoop: forMode:] et passez le thread principal ou un autre thread que vous connaissez. Si vous faites cela, vous voudrez probablement appeler - [NSURLConnection unscheduleInRunLoop: forMode:] d'abord sinon vous pourriez recevoir les données dans plusieurs threads (ou du moins c'est ce que la documentation semble suggérer). Utilisez quelque chose comme + [NSData dataWithContentsOfURL: options: error:] et cela simplifiera également votre opération car vous pouvez en faire une opération non simultanée à la place.

  3. Variante sur # 4: utilisez + [NSURLConnection sendSynchronousRequest: returnResponse: error:].

Si vous pouvez vous en sortir, faites le # 4 ou le # 5.

+0

Merci pour votre réponse. Le code que j'ai posté est juste un exemple simple que j'ai créé à des fins de test. Le programme actuel utilise une API qui est hors de mon contrôle et est de nature asynchrone. Cependant, vous m'avez amené à relire correctement 2 articles intéressants qui ont résolu le problème! Je posterai une réponse pour que tout le monde comprenne le problème, mais merci d'avoir pris le temps et vous avez raison sur les boucles de course! –

Questions connexes