web-dev-qa-db-fra.com

Thread de travail NSOperation et NSOperationQueue vs thread principal

Je dois effectuer une série d'opérations de téléchargement et d'écriture de base de données dans mon application. J'utilise NSOperation et NSOperationQueue pour la même chose.

C'est un scénario d'application:

  • Récupérer tous les codes postaux d'un lieu.
  • Pour chaque code postal chercher toutes les maisons.
  • Pour chaque maison chercher les détails de l'habitant

Comme dit, j'ai défini un NSOperation pour chaque tâche. Dans le premier cas (Task1), j'envoie une demande au serveur pour récupérer tous les codes postaux. Le délégué au sein de la NSOperation recevra les données. Ces données sont ensuite écrites dans la base de données. L'opération de base de données est définie dans une classe différente. De NSOperation class J'appelle la fonction write définie dans la classe database.

Ma question est de savoir si l'opération d'écriture de base de données se produit dans le thread principal ou dans un thread d'arrière-plan? Comme je l'appelais dans un NSOperation, je m'attendais à ce qu'il s'exécute dans un autre thread (pas MainThread) que le NSOperation. Quelqu'un peut-il s'il vous plaît expliquer ce scénario lorsqu'il s'agit de NSOperation et NSOperationQueue.

80
Zach

Ma question est de savoir si l'opération d'écriture de base de données se produit dans le thread principal ou dans un thread d'arrière-plan?

Si vous créez un NSOperationQueue à partir de zéro comme dans:

NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];

Ce sera dans un fil de fond:

Les files d'opérations fournissent généralement les threads utilisés pour exécuter leurs opérations. Dans OS X version 10.6 et ultérieure, les files d’opérations utilisent la bibliothèque libdispatch (également connue sous le nom de Grand Central Dispatch) pour lancer l’exécution de leurs opérations. En conséquence, les opérations sont toujours exécutées sur un thread séparé , qu'elles soient désignées comme opérations simultanées ou non.

Sauf si vous utilisez le mainQueue:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

Vous pouvez également voir le code comme ceci:

NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{

   // Background work

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // Main thread work (UI usually)
    }];
}];

Et la version de GCD:

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
             {
              // Background work            
             dispatch_async(dispatch_get_main_queue(), ^(void)
              {
                   // Main thread work (UI usually)                          
              });
});

NSOperationQueue donne un contrôle plus précis de ce que vous voulez faire. Vous pouvez créer des dépendances entre les deux opérations (télécharger et enregistrer dans la base de données). Pour transmettre les données d'un bloc à l'autre, vous pouvez par exemple supposer qu'un NSData viendra du serveur de sorte que:

__block NSData *dataFromServer = nil;
NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakDownloadOperation = downloadOperation;

[weakDownloadOperation addExecutionBlock:^{
 // Download your stuff  
 // Finally put it on the right place: 
 dataFromServer = ....
 }];

NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation;

 [weakSaveToDataBaseOperation addExecutionBlock:^{
 // Work with your NSData instance
 // Save your stuff
 }];

[saveToDataBaseOperation addDependency:downloadOperation];

[myQueue addOperation:saveToDataBaseOperation];
[myQueue addOperation:downloadOperation];

Edit: Pourquoi j'utilise __weak référence pour les opérations, peut être trouvé ici . Mais en un mot, il faut éviter de retenir les cycles.

173
Rui Peres

Si vous souhaitez effectuer l'opération d'écriture de base de données dans le thread d'arrière-plan, vous devez créer un NSManagedObjectContext pour ce thread.

Vous pouvez créer l’arrière-plan NSManagedObjectContext dans la méthode de démarrage de votre sous-classe NSOperation correspondante.

Vérifiez les docs Apple pour accès simultané avec les données de base.

Vous pouvez également créer un NSManagedObjectContext qui exécute les demandes dans son propre thread d'arrière-plan en le créant avec NSPrivateQueueConcurrencyType et en effectuant les demandes dans son performBlock: méthode.

16
serrrgi

De NSOperationQueue

Dans iOS 4 et versions ultérieures, les files d'attente d'opérations utilisent Grand Central Dispatch pour exécuter des opérations. Avant iOS 4, ils créaient des threads distincts pour les opérations non simultanées et lançaient des opérations simultanées à partir du thread actuel.

Alors,

[NSOperationQueue mainQueue] // added operations execute on main thread
[NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread

Dans votre cas, vous pouvez créer votre propre "thread de base de données" en sous-classant NSThread et en lui envoyant des messages avec performSelector:onThread:.

11
ilya n.

Le thread d'exécution de NSOperation dépend du NSOperationQueue où vous avez ajouté l'opération. Regardez cette déclaration dans votre code -

[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class

Tout cela suppose que vous n’ayez plus fait de thread dans la méthode main de NSOperation, qui est le monstre réel dans lequel les instructions de travail que vous avez écrites (devraient être écrites).

Cependant, en cas d'opérations simultanées, le scénario est différent. La file d'attente peut générer un thread pour chaque opération simultanée. Bien que ce ne soit pas garanti et que cela dépend des ressources système par rapport aux demandes en ressources opérationnelles à ce stade du système. Vous pouvez contrôler la simultanéité de la file d’opérations par sa propriété maxConcurrentOperationCount.

EDIT -

J'ai trouvé votre question intéressante et j'ai moi-même effectué quelques analyses/journalisation. J'ai NSOperationQueue créé sur le fil principal comme ceci -

self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease];

NSLog(@"Operation queue creation. current thread = %@ \n main thread = %@", [NSThread currentThread], [NSThread mainThread]);
self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency

Et puis, j'ai créé un NSOperation et je l'ai ajouté à l'aide de addOperation. Dans la méthode principale de cette opération quand j'ai vérifié le fil actuel,

NSLog(@"Operation obj =  %@\n current thread = %@ \n main thread = %@", self, [NSThread currentThread], [NSThread mainThread]);

ce n'était pas comme fil conducteur. Et, trouvé que l'objet de fil actuel n'est pas l'objet de fil principal.

Ainsi, la création personnalisée d'une file d'attente sur le thread principal (sans concurrence entre ses opérations) ne signifie pas nécessairement que les opérations seront exécutées en série sur le thread principal lui-même.

9
Ashok

Le résumé de la documentation est operations are always executed on a separate thread (post iOS 4 implique les files d’opérations sous-jacentes à GCD).

Il est trivial de vérifier qu'il fonctionne bien sur un thread non principal:

NSLog(@"main thread? %@", [NSThread isMainThread] ? @"YES" : @"NO");

Lors de l'exécution dans un thread, il est simple d'utiliser GCD/libdispatch pour exécuter quelque chose sur le thread principal, qu'il s'agisse de données de base, d'interface utilisateur ou d'un autre code requis pour s'exécuter sur le thread principal:

dispatch_async(dispatch_get_main_queue(), ^{
    // this is now running on the main thread
});
1
duncanwilcox