Pourquoi avons-nous besoin du mot-clé async?


19

Je viens de commencer à jouer avec async / wait dans .Net 4.5. Une chose qui m'intéresse au départ, pourquoi le mot-clé async est-il nécessaire? L'explication que j'ai lue était que c'est un marqueur, donc le compilateur sait qu'une méthode attend quelque chose. Mais il semble que le compilateur devrait être capable de comprendre cela sans mot-clé. Que fait-il d'autre?


2
Voici un très bon article de blog (série) qui décrit en détail le mot-clé en C # et les fonctionnalités associées en F #.
paul

Je pense que c'est principalement un marqueur pour le développeur, et non pour le compilateur.
CodesInChaos


@svick merci, c'est ce que je cherchais. Est parfaitement logique maintenant.
ConditionRacer

Réponses:


23

Il y a plusieurs réponses ici, et toutes parlent de ce que font les méthodes asynchrones, mais aucune ne répond à la question, c'est pourquoi asyncest nécessaire en tant que mot-clé qui va dans la déclaration de fonction.

Ce n'est pas "de demander au compilateur de transformer la fonction d'une manière spéciale"; awaitseul pouvait le faire. Pourquoi? Parce que C # a déjà un autre mécanisme où la présence d'un mot - clé spécial dans le corps de méthode provoque le compilateur pour effectuer extrême (et très similaire à async/await) des transformations sur le corps de la méthode: yield.

Sauf que ce yieldn'est pas son propre mot-clé en C #, et comprendre pourquoi l'expliquera asyncaussi. Contrairement à la plupart des langages qui prennent en charge ce mécanisme, en C #, vous ne pouvez pas dire yield value;à la yield return value;place. Pourquoi? Parce qu'il a été ajouté au langage après que C # existait déjà, et il était tout à fait raisonnable de supposer que quelqu'un, quelque part, aurait pu utiliser yieldle nom d'une variable. Mais parce qu'il n'y avait pas de scénario préexistant dans lequel <variable name> returnétait syntaxiquement correct, a yield returnété ajouté au langage pour permettre d'introduire des générateurs tout en maintenant une compatibilité à 100% avec le code existant.

Et c'est pourquoi a asyncété ajouté comme modificateur de fonction: pour éviter de casser le code existant utilisé awaitcomme nom de variable. Puisqu'aucune asyncméthode n'existait déjà, aucun ancien code n'est invalidé et dans le nouveau code, le compilateur peut utiliser la présence de la asyncbalise pour savoir qui awaitdoit être traité comme un mot-clé et non comme un identifiant.


3
C'est à peu près ce que dit également cet article (initialement publié par @svick dans les commentaires), mais personne ne l'a jamais publié comme réponse. Cela n'a pris que deux ans et demi! Merci :)
ConditionRacer

Cela ne signifie-t-il pas que dans certaines versions futures, l'exigence de spécifier le asyncmot - clé peut être supprimée? De toute évidence, il s'agirait d'une interruption de la Colombie-Britannique, mais on pourrait penser qu'elle affecte très peu de projets et qu'il s'agit d'une simple mise à niveau (c.-à-d. Changer un nom de variable).
Questions sur Quolonel,

6

il change la méthode d'une méthode normale en un objet avec rappel qui nécessite une approche totalement différente pour la génération de code

et quand quelque chose de drastique comme ça arrive, il est habituel de le signifier clairement (nous avons appris cette leçon de C ++)


Donc, c'est vraiment quelque chose pour le lecteur / programmeur, et pas tellement pour le compilateur?
ConditionRacer

@ Justin984, cela aide certainement à signaler au compilateur quelque chose de spécial doit se produire
ratchet freak

Je suppose que je suis curieux de savoir pourquoi le compilateur en a besoin. Ne pourrait-il pas simplement analyser la méthode et voir que l'attente se trouve quelque part dans le corps de la méthode?
ConditionRacer

cela aide lorsque vous souhaitez planifier plusieurs asyncs en même temps afin qu'ils ne s'exécutent pas l'un après l'autre mais simultanément (si les threads sont disponibles au moins)
ratchet freak

@ Justin984: Toutes les méthodes renvoyant n'ont pas Task<T>réellement les caractéristiques d'une asyncméthode - par exemple, vous pouvez simplement exécuter une logique métier / usine, puis déléguer à une autre méthode renvoyant Task<T>. asyncLes méthodes ont un type de retour Task<T>dans la signature mais ne retournent pas réellement a Task<T>, elles renvoient juste a T. Afin de comprendre tout cela sans le asyncmot clé, le compilateur C # devrait effectuer toutes sortes d'inspections approfondies de la méthode, ce qui la ralentirait probablement beaucoup et conduirait à toutes sortes d'ambiguïtés.
Aaronaught

4

L'idée avec des mots clés comme "async" ou "unsafe" est de lever toute ambiguïté quant à la façon dont le code qu'ils modifient doit être traité. Dans le cas du mot-clé async, il indique au compilateur de traiter la méthode modifiée comme quelque chose qui n'a pas besoin d'être renvoyé immédiatement. Cela permet au thread où cette méthode est utilisée de continuer sans avoir à attendre les résultats de cette méthode. C'est effectivement une optimisation de code.


Je suis tenté de voter contre, car si le premier paragraphe est correct, la comparaison avec les interruptions est incorrecte (à mon avis éclairé).
paul

Je les vois comme quelque peu analogues en termes d'objectif, mais je vais les supprimer pour plus de clarté.
World Engineer

Les interruptions sont plus des événements que du code asynchrone dans mon esprit - elles provoquent un changement de contexte et s'exécutent jusqu'à la fin du code normal (et le code normal peut être complètement ignorant de son exécution, aussi), tandis qu'avec le code asynchrone, la méthode peut pas terminé avant la reprise du thread appelant. Je vois ce que vous essayez de dire sous différents mécanismes nécessitant différentes implémentations sous le capot, mais les interruptions ne m'ont tout simplement pas permis d'illustrer ce point. (+1 pour le reste, btw.)
paul

0

OK, voici mon point de vue à ce sujet.

Il y a quelque chose appelé coroutines qui est connu depuis des décennies. ("Knuth and Hopper" -class "pendant des décennies") Ce sont des généralisations de sous - programmes , dans la mesure où non seulement ils obtiennent et relâchent le contrôle au début de la fonction et la déclaration de retour, mais ils le font également à des points spécifiques ( points de suspension ). Un sous-programme est une coroutine sans point de suspension.

Ils sont simples à mettre en œuvre avec des macros C, comme le montre le document suivant sur les "protothreads". ( http://dunkels.com/adam/dunkels06protothreads.pdf ) Lisez-le. J'attendrai...

L'essentiel, c'est que les macros créent un gros switchet une caseétiquette à chaque point de suspension. À chaque point de suspension, la fonction stocke la valeur de l' caseétiquette immédiatement suivante , afin qu'elle sache où reprendre l'exécution la prochaine fois qu'elle sera appelée. Et il rend le contrôle à l'appelant.

Cela se fait sans modifier le flux apparent de contrôle du code décrit dans le "protothread".

Imaginez maintenant que vous avez une grande boucle appelant à tour de rôle toutes ces "protothreads" et que vous obtenez simultanément l'exécution de "protothreads" sur un seul thread.

Cette approche présente deux inconvénients:

  1. Vous ne pouvez pas conserver l'état dans les variables locales entre les reprises.
  2. Vous ne pouvez pas suspendre le "protothread" à partir d'une profondeur d'appel arbitraire. (tous les points de suspension doivent être au niveau 0)

Il existe des solutions de contournement pour les deux:

  1. Toutes les variables locales doivent être remontées au contexte de la protothread (contexte qui est déjà requis par le fait que la protothread doit stocker son prochain point de reprise)
  2. Si vous sentez que vous avez vraiment besoin d'appeler une autre protothread à partir d'une protothread, "générez" une enfant protothread et suspendez jusqu'à la fin de l'enfant.

Et si vous aviez le support du compilateur pour faire le travail de réécriture que font les macros et la solution de contournement, vous pouvez simplement écrire votre code de prototread comme vous le souhaitez et insérer des points de suspension avec un mot clé.

Et voici ce asyncet awaitsont tous sur: la création (Stackless) coroutines.

Les coroutines en C # sont réifiées en tant qu'objets de classe (générique ou non générique) Task.

Je trouve ces mots clés très trompeurs. Ma lecture mentale est:

  • async comme "suspensible"
  • await comme "suspendre jusqu'à la fin de"
  • Task comme "futur ..."

Maintenant. Avons-nous vraiment besoin de marquer la fonction async? En plus de dire qu'il devrait déclencher les mécanismes de réécriture de code pour faire de la fonction une coroutine, il résout certaines ambiguïtés. Considérez ce code.

public Task<object> AmIACoroutine() {
    var tcs = new TaskCompletionSource<object>();
    return tcs.Task;
}

En supposant que ce asyncn'est pas obligatoire, est-ce une coroutine ou une fonction normale? Le compilateur devrait-il le réécrire en coroutine ou non? Les deux pourraient être possibles avec une sémantique éventuelle différente.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.