Différence entre dispatch_async et dispatch_sync sur la file d'attente série?

125

J'ai créé une file d'attente série comme celle-ci:

    dispatch_queue_t _serialQueue = dispatch_queue_create("com.example.name", DISPATCH_QUEUE_SERIAL);

Quelle est la différence entre dispatch_asyncappelé comme ça

 dispatch_async(_serialQueue, ^{ /* TASK 1 */ });
 dispatch_async(_serialQueue, ^{ /* TASK 2 */ });

Et dispatch_syncappelé comme ça sur cette file d'attente série?

 dispatch_sync(_serialQueue, ^{ /* TASK 1 */ });
 dispatch_sync(_serialQueue, ^{ /* TASK 2 */ });

Je crois comprendre que, quelle que soit la méthode d'expédition utilisée, TASK 1sera exécutée et complétée avant TASK 2, n'est-ce pas?

Développeur JRG
la source

Réponses:

409

Oui. L'utilisation de la file d'attente série garantit l'exécution en série des tâches. La seule différence est que dispatch_syncne revient qu'après la fin du bloc alors quedispatch_async retour après avoir été ajouté à la file d'attente et peut ne pas être terminé.

pour ce code

dispatch_async(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_async(_serialQueue, ^{ printf("3"); });
printf("4");

Il peut imprimer 2413ou 2143ou 1234mais 1toujours avant3

pour ce code

dispatch_sync(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_sync(_serialQueue, ^{ printf("3"); });
printf("4");

il s'imprime toujours 1234


Remarque: pour le premier code, il ne s'imprimera pas1324 . Car printf("3")est expédié après avoir printf("2") été exécuté. Et une tâche ne peut être exécutée qu'après avoir été distribuée.


Le temps d'exécution des tâches ne change rien. Ce code s'imprime toujours12

dispatch_async(_serialQueue, ^{ sleep(1000);printf("1"); });
dispatch_async(_serialQueue, ^{ printf("2"); });

Ce qui peut arriver est

  • Thread 1: dispatch_async une tâche chronophage (tâche 1) vers la file d'attente série
  • Thread 2: démarrer l'exécution de la tâche 1
  • Thread 1: dispatch_async une autre tâche (tâche 2) vers la file d'attente série
  • Thread 2: tâche 1 terminée. démarrer l'exécution de la tâche 2
  • Thread 2: tâche 2 terminée.

et tu vois toujours 12

Bryan Chen
la source
7
il peut également imprimer 2134 et 1243
Matteo Gobbi
ma question est pourquoi nous ne l'avons pas fait comme d'habitude? printf("1");printf("2") ;printf("3") ;printf("4")- comparé àdispatch_sync
androniennn
@androniennn pour le deuxième exemple? car un autre thread peut s'exécuter dispatch_sync(_serialQueue, ^{ /*change shared data*/ });en même temps.
Bryan Chen
1
@ asma22 Il est très utile de partager un objet non thread-safe entre plusieurs threads / files d'attente de distribution. Si vous accédez uniquement à l'objet dans une file d'attente série, vous savez que vous y accédez en toute sécurité.
Bryan Chen
1
Je veux dire une exécution en série . Du point de vue que toutes les tâches sont exécutées en série par rapport aux autres tâches de la même file d'attente. Bien sûr, il peut toujours s'agir d'autres files d'attente. C'est tout l'intérêt de GCD que les tâches peuvent être distribuées et exécutées simultanément.
Bryan Chen
19

La différence entre dispatch_syncet dispatch_asyncest simple.

Dans vos deux exemples, TASK 1sera toujours exécuté avant TASK 2car il a été distribué avant lui.

Dans l' dispatch_syncexemple, cependant, vous ne distribuerez pas TASK 2avant d'avoir TASK 1été expédié et exécuté . C'est ce qu'on appelle le "blocage" . Votre code attend (ou "bloque") jusqu'à ce que la tâche s'exécute.

Dans l' dispatch_asyncexemple, votre code n'attendra pas la fin de l'exécution. Les deux blocs seront envoyés (et mis en file d'attente) dans la file d'attente et le reste de votre code continuera à s'exécuter sur ce thread. Ensuite, à un moment donné dans le futur (selon ce qui a été envoyé à votre file d'attente), Task 1s'exécutera puis Task 2s'exécutera.

Dave DeLong
la source
2
Je pense que votre ordre est erroné. Le premier exemple est asyncquelle est la version non bloquante
Bryan Chen
J'ai modifié votre réponse à ce que je pense que vous vouliez dire . Si ce n'est pas le cas, veuillez le modifier et clarifier.
JRG-Developer
1
Que faire si vous appelez dispatch_sync puis dispatch_async sur la même file d'attente? (et vice versa)
0xSina
1
Sur une file d'attente série, les deux tâches sont toujours exécutées l'une après l'autre. Dans le premier cas, l'appelant attend la fin du premier bloc mais n'attend pas le second bloc. Dans le second cas, l'appelant n'attend pas la fin du premier bloc, mais attend le second bloc. Mais comme la file d'attente exécute les blocs dans l'ordre, l'appelant attend effectivement que les deux se terminent.
gnasher729
1
Un bloc peut également faire un dispatch_async sur sa propre file d'attente (en ajoutant d'autres blocs qui seront exécutés plus tard); dispatch_sync sur la propre file d'attente série ou la file d'attente principale entraînerait un blocage. Dans cette situation, l'appelant attendra la fin du bloc d'origine, mais pas les autres blocs. Rappelez-vous simplement: dispatch_sync met le bloc à la fin de la file d'attente, la file d'attente exécute le code jusqu'à ce que ce bloc soit terminé, puis dispatch_sync revient. dispatch_async ajoute simplement le bloc à la fin de la file d'attente.
gnasher729
5

Tout est lié à la file d'attente principale. Il y a 4 permutations.

i) File d'attente série, répartition asynchrone: Ici les tâches s'exécuteront l'une après l'autre, mais le thread principal (effet sur l'interface utilisateur) n'attendra pas le retour

ii) File d'attente série, synchronisation de répartition: Ici, les tâches s'exécuteront l'une après l'autre, mais le thread principal (effet sur l'interface utilisateur) affichera un décalage

iii) File d'attente simultanée, répartition asynchrone: Ici, les tâches s'exécuteront en parallèle et le thread principal (effet sur l'interface utilisateur) n'attendra pas le retour et sera fluide.

iv) File d'attente simultanée, synchronisation de distribution: Ici, les tâches s'exécuteront en parallèle, mais le thread principal (effet sur l'interface utilisateur) affichera un décalage

Votre choix de file d'attente simultanée ou série dépend de si vous avez besoin d'une sortie d'une tâche précédente pour la suivante. Si vous dépendez de la tâche précédente, adoptez la file d'attente série sinon prenez la file d'attente simultanée.

Et enfin, c'est une façon de revenir au fil conducteur lorsque nous en avons terminé avec notre entreprise:

DispatchQueue.main.async {
     // Do something here
}
rd_
la source