Tout d'abord, je vous remercie pour vos aimables paroles. C'est en effet une fonctionnalité impressionnante et je suis heureux d'avoir été une petite partie de cela.
Si tout mon code est en tournant lentement async, pourquoi ne pas simplement faire tout asynchrone par défaut?
Eh bien, tu exagères; tous votre code n'est pas asynchrone. Lorsque vous ajoutez deux "plaine" entiers ensemble, vous n'êtes pas à l'attente du résultat. Lorsque vous ajoutez deux futurs entiers pour obtenir un troisième avenir entier -- parce que c'est ce qu' Task<int>
, c'est un entier que vous allez avoir accès à de l'avenir-bien sûr, vous aurez probablement être en attente de la suite.
La principale raison de ne pas faire tout async est parce que le but de async/await est de rendre plus facile d'écrire du code dans un monde avec beaucoup de temps de latence élevé des opérations. La grande majorité de vos opérations sont pas de latence élevée, de sorte qu'il ne ferait aucun sens de prendre le gain de performances qui réduit les temps d'attente. Plutôt, une clé de quelques-uns de vos opérations de haut de latence, et ces opérations sont à l'origine de l'infestation de zombies de async dans le code.
si la performance est le seul problème, sûrement quelques petits malins optimisations peuvent supprimer la surcharge automatiquement lorsqu'il n'est pas nécessaire.
En théorie, la théorie et la pratique sont similaires. Dans la pratique, elles ne le sont jamais.
Permettez-moi de vous donner trois points face à ce type de transformation suivie par une optimisation de passe.
Premier point est: async en C#/VB/F# est essentiellement une forme limitée de la poursuite de passage. Une énorme quantité de recherche de la fonctionnelle de la langue de la communauté, il est passé en trouver de nouvelles manières d'identifier comment optimiser le code qui fait un usage intensif de la continuation passing style. L'équipe du compilateur aurait probablement pour résoudre des problèmes très similaires dans un monde où "async" est la valeur par défaut et la non-async méthodes devaient être identifiés et de-async-identifiés. L'équipe C# n'est pas vraiment intéressé à prendre sur l'ouverture à des problèmes de recherche, de sorte que c'est des gros points contre à droite.
Un deuxième point est que le C# n'a pas le niveau de "transparence référentielle" qui rend ces sortes d'optimisations plus docile. Par "référentiel de transparence" je veux dire le bien que la valeur d'une expression ne dépend pas de quand elle est évaluée. Des Expressions comme 2 + 2
sont referentially transparent; vous pouvez faire de l'évaluation au moment de la compilation, si vous voulez, ou la reporter jusqu'à exécution et obtenir la même réponse. Mais une expression comme x+y
ne peuvent pas être déplacés dans le temps parce que x et y peut être en train de changer au fil du temps.
Async rend beaucoup plus difficile de raisonner sur les cas où un effet secondaire qui va arriver. Avant asynchrone, si vous avez dit:
M();
N();
et M()
a void M() { Q(); R(); }
, et N()
a void N() { S(); T(); }
, et R
et S
produire des effets secondaires, alors vous savez que R est l'effet secondaire se produit avant de S effets secondaires. Mais si vous avez async void M() { await Q(); R(); }
puis tout à coup qui va à la fenêtre. Vous n'avez aucune garantie qu' R()
va se produire avant ou après l' S()
(sauf si bien sûr, M()
est attendue; mais, bien entendu, de son Task
n'a pas besoin d'attendre jusqu'à l'après - N()
.)
Maintenant, imaginez que cette propriété de ne plus savoir dans quel ordre les effets secondaires se produisent dans s'applique à chaque bout de code dans votre programme à l'exception de ceux que l'optimiseur gère de-async-identifier. Fondamentalement, vous n'avez aucune idée de plus qui des expressions à évaluer dans quel ordre, ce qui signifie que toutes les expressions doivent être referentially transparent, ce qui est difficile dans un langage comme C#.
Un troisième point est que vous devez poser la question "pourquoi est asynchrone de si spécial?" Si vous allez à faire valoir que chaque opération doit être en fait un Task<T>
, alors vous devez être en mesure de répondre à la question "pourquoi ne pas Lazy<T>
?" ou "pourquoi ne pas Nullable<T>
?" ou "pourquoi ne pas IEnumerable<T>
?" Car on peut tout aussi facilement le faire. Pourquoi ne devrait-elle pas être le cas que chaque opération est levé pour prendre la valeur null? Ou chaque opération est paresseusement calculée et le résultat est mis en cache pour plus tard, ou le résultat de chaque opération est une séquence de valeurs au lieu d'une seule valeur. Ensuite, vous devez essayer d'optimiser ces situations où vous vous dites "oh, ce ne doit jamais être nulle, de sorte que je peux générer un meilleur code", et ainsi de suite. (Et, en fait, le compilateur C# ne font pour la levée de l'arithmétique.)
Point de l'être: il n'est pas clair pour moi qu' Task<T>
est en fait de spécial pour justifier ce beaucoup de travail.
Si ces sortes de choses qui vous intéressent alors je vous recommande d'étudier les langages fonctionnels comme Haskell, qui ont beaucoup plus référentielle de la transparence et de permis tous les types de cas de l'évaluation et de faire la mise en cache automatique. Haskell a également beaucoup plus de soutien dans son type de système pour les sortes de "monadique liftings" que je viens d'évoquer.