1

J'ai une méthode:Utilisation de la file d'attente série d'expédition pour appeler une méthode qui accepte le bloc d'achèvement en tant que paramètre

-(void) dataForRequest:(NSMutableURLRequest*)url withCallback:(void (^)(NSData* data))callbackBlock` 

qui reçoit les données de l'URL en arrière-plan, puis appelle la callbackBlock avec les données reçues.

J'appelle cela sur une file d'attente de série comme:

dispatch_async(my_serial_queue(), ^(void) { 
    [dataForRequest:SOME_REQUEST withCallback:^(NSData *data) { 
     // do something with data here. 
}]; 

}); 

La raison pour laquelle je fais cela sur une file d'attente série est parce que je veux que les demandes pour obtenir appelé un à la fois, en ordre dans qui ils ont été faits.

Mais le problème que je suis confronté est, les demandes sont faites dans l'ordre, mais le callbackBlock que je passe à la méthode est pas obtenir appelé dans ordre.

Pour exemple, request_1, request_2 et request_3 sont soumis à la file d'attente de série dans le bon ordre, mais callBackBlock_1, callBackBlock_2 et callBackBlock_3 ne sont pas exécutées dans le même ordre.

Je comprends pourquoi cela se produit, mais je ne suis pas en mesure de trouver une solution à ce problème. Toute aide sera appréciée. Merci!

+2

Je ne sais pas comment le faire en utilisant GCD. Mais vous pouvez utiliser NSOperationQueue où vous pouvez facilement spécifier une dépendance entre NSOperations afin que la 2ème tâche commence seulement après l'achèvement de la première tâche. –

+0

@AlokRao Votre solution n'est pas tout à fait la même: l'idée originale était d'appeler les requêtes dans l'ordre (par exemple en utilisant une FIFO), puis de les exécuter _concurrently_. Enfin, lorsque tous sont complets, traiter les données reçues dans l'ordre. Une optimisation potentielle serait de traiter les données à leur arrivée ET si les données précédentes ont déjà été traitées.Faire cela avec NSOperation est possible, mais très lourd. Faire cela avec GCD est possible aussi - mais difficile;) – CouchDeveloper

Répondre

-2

Il y a beaucoup d'implémentations possibles, mais le principe de base ici est que vous voulez qu'une requête soit complétée avant d'émettre la suivante.

Comme mentionné dans les commentaires, vous pouvez créer un NSOperation pour encapsuler chaque demande. Signalez seulement l'opération comme terminée après avoir appelé votre bloc d'achèvement. Cette approche est probablement la meilleure et la moins sujette aux bugs.

1

Voici une solution implémentée dans Swift, basée uniquement sur GCD. Cette approche est purement asynchrone, à l'exception de la dernière instruction dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER) qui n'existe que pour maintenir le thread principal en vie jusqu'à ce que toutes les tâches et suites aient été complétées.

Il ne devrait pas être trop difficile de porter cet échantillon à Objective-C. Le point crucial est probablement d'obtenir les références importées du dispatch_groups.

Le code ci-dessous crée dix tâches asynchrones avec les noms "1", "2", .. et "10". La durée de chaque tâche est aléatoire entre 0 et 8 secondes. Le continuation de chaque tâche affichera son nom. Donc, la sortie doit être telle que les noms seront imprimés dans le même ordre que celui qui a été programmé - quelle que soit la durée. Il peut être le cas, que les noms ont déjà été imprimés alors qu'il y a encore des tâches en cours d'exécution.

import Foundation 


let queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL) 


func task(duration: Double, completion:()->()) { 
    let t = dispatch_time(DISPATCH_TIME_NOW, (Int64)(duration * Double(NSEC_PER_SEC))) 
    dispatch_after(t, dispatch_get_global_queue(0, 0)) { 
     completion() 
    } 
} 


func test(params: [String], continuation:()->()) { 
    let g0 = dispatch_group_create() 
    dispatch_group_enter(g0) 
    var g_prev = g0 

    for s in params { 
     let g = dispatch_group_create() 
     dispatch_group_enter(g) 
     let t = Double(arc4random() % 8) 
     print("schedule task \"\(s)\" with duration \(t)") 
     let gp = g_prev 
     task(t) { 
      print("finished task \"\(s)\"") 
      dispatch_group_notify(gp, queue) { [g] 
       print(s) 
       dispatch_group_leave(g) 
      } 
     } 
     g_prev = g 

    } 
    dispatch_group_leave(g0) 

    dispatch_group_notify(g_prev, dispatch_get_global_queue(0, 0)) { 
     continuation() 
    } 
} 


let sem = dispatch_semaphore_create(0) 
test(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) { 
    print("finished") 
    dispatch_semaphore_signal(sem) 
} 

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER) 
+0

Vous dites que "Cela peut être le cas, des noms ont déjà été imprimés alors qu'il y a encore des tâches en cours d'exécution." Mais dans mon cas, j'ai besoin que la tâche soit terminée avant d'appeler le callbackBlock. Parce que vous voyez, le 'callBackBlock' utilise les données récupérées de l'URL. –

+0

@SagarD L'approche ci-dessus peut être utilisée pour: un tableau d'URL. Créez pour chaque URL une tâche asynchrone qui récupère les données d'un serveur. Planifiez les tâches dans l'ordre, puis traitez les données renvoyées _en order_ (c'est-à-dire, traiter les données de 1. URL, puis les données de 2. URL, etc.) Lorsque toutes les données ont été traitées, la fonction 'test' appelle son gestionnaire de complétion . c'est ce que tu veux? Remarque: il s'agit d'un détail de mise en œuvre _comment de nombreuses requêtes s'exécuteront simultanément. Si vous utilisez un 'NSURLSession' vous pouvez configurer le nombre de demandes simultanées maximum. – CouchDeveloper