104 votes

Pourquoi toutes les fonctions ne devraient-elles pas être async par défaut?

La async-attendent modèle de .net 4.5 changement de paradigme. C'est presque trop beau pour être vrai.

J'ai été portage de certaines IO-lourds de code asynchrone attendre parce que le blocage est une chose du passé.

Très peu de gens sont en comparant asynchrone attendent à une infestation de zombies et je l'ai trouvé assez juste. Async code apprécie les autres async code (vous avez besoin d'une fonction async afin d'attendre sur une fonction async). Et de plus en plus de fonctions de devenir async et cela ne cesse de croître dans votre base de code.

Changement des fonctions async est un peu répétitif et peu imaginatif de travail. Jetez un async mot-clé dans la déclaration, enveloppez-le retour de la valeur en Task<> et que vous êtes assez bien fait. C'est assez troublant de savoir comment facile l'ensemble du processus est, et bientôt un texte-en remplacement de script permet d'automatiser la plupart des "portage" pour moi.

Et maintenant, la question.. Si tout mon code est en tournant lentement async, pourquoi ne pas simplement faire tout asynchrone par défaut?

Pour une raison évidente, je suppose est la performance. Async-attendre a un de ses frais généraux et le code qui n'a pas besoin d'être asynchrone, de préférence, ne pas le faire. Mais 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. J'ai lu sur la "voie rapide" de l'optimisation, et il me semble qu'elle seule doit prendre soin de la plupart.

C'est peut-être comparable au changement de paradigme provoqué par les éboueurs. Au début GC jours, libérant ainsi votre propre mémoire a été certainement plus efficace. Mais les masses ont choisi de continuer la collecte automatique en faveur de plus sûr, plus simple de code qui pourrait être moins efficace (et même que l'on n'est pas vrai). Peut-être que cela devrait être le cas ici? Pourquoi ne pas toutes les fonctions asynchrones?

120voto

Eric Lippert Points 300275

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.

21voto

Reed Copsey Points 315315

Pourquoi ne pas toutes les fonctions asynchrones?

La Performance est l'une des raisons, comme vous l'avez mentionné. Notez que la "voie rapide" de l'option que vous avez associé permet d'améliorer les performances dans le cas d'une Tâche terminée, mais il nécessite toujours beaucoup plus de directives et de frais généraux par rapport à un seul appel de méthode. En tant que tel, même avec la "voie rapide" en place, vous êtes en ajoutant beaucoup de complexité et la charge à chaque appel de méthode asynchrone.

Rétro-compatibilité, ainsi que la compatibilité avec d'autres langues (y compris l'interopérabilité des scénarios), serait également devenir problématique.

L'autre est une question de la complexité et de l'intention. Les opérations asynchrones ajouter de la complexité - dans de nombreux cas, les fonctions du langage de cacher cela, mais il ya de nombreux cas où les méthodes d'élaboration async certainement ajoute à la complexité de leur utilisation. Cela est particulièrement vrai si vous ne disposez pas d'un contexte de synchronisation, comme les méthodes asynchrones alors peut facilement se retrouver causant des problèmes de threads qui sont inattendus.

En outre, il existe de nombreuses routines qui ne sont pas, de par leur nature, asynchrone. Ceux qui font plus de sens que les opérations synchrones. Forçant Math.Sqrt être Task<double> Math.SqrtAsync serait ridicule, par exemple, comme il n'y a aucune raison pour que ce soit asynchrone. Au lieu d'avoir async pousser à travers votre application, vous feriez à la fin avec l' await propogating partout.

Cela permettrait également de briser le paradigme actuel complètement, ainsi que causer des problèmes avec des propriétés (qui sont effectivement juste la méthode des paires.. iraient-ils async trop?), et avoir d'autres répercussions tout au long de la conception du cadre et de la langue.

Si vous faites beaucoup de IO lié au travail, vous aurez tendance à trouver que l'utilisation d' async omniprésente est un grand plus, un grand nombre de vos routines sera async. Toutefois, lorsque vous commencez à faire des CPU de travail, en général, faire des choses async est effectivement pas bon c'est de cacher le fait que vous êtes à l'aide de cycles de PROCESSEUR sous une API qui semble être asynchrone, mais c'est vraiment pas nécessairement vraiment asynchrone.

3voto

usr Points 74796

La performance mise à part - async peut avoir un coût de productivité. Sur le client (WinForms, WPF, Windows Phone), la productivité est améliorée. Mais sur le serveur ou dans d'autres scénarios non liés à l'interface utilisateur, vous payez la productivité. Vous ne voulez certainement pas y aller asynchrone par défaut. Utilisez-le lorsque vous avez besoin des avantages de l'évolutivité.

Utilisez-le quand à l'endroit idéal. Dans d'autres cas, ne le faites pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X