ValueTask<T>
n'est pas un sous-ensemble de Task<T>
c'est un superset .
ValueTask<T>
est une union discriminée d'un T et d'un Task<T>
ce qui fait qu'il n'y a pas d'allocation pour ReadAsync<T>
pour retourner de manière synchrone une valeur T dont il dispose (contrairement à l'utilisation de Task.FromResult<T>
qui doit allouer un Task<T>
instance). ValueTask<T>
est attendable, de sorte que la plupart des consommations d'instances seront indiscernables de celles effectuées avec un fichier Task<T>
.
ValueTask, étant une structure, permet d'écrire des méthodes asynchrones qui n'allouent pas de mémoire lorsqu'elles s'exécutent de manière synchrone, sans compromettre la cohérence de l'API. Imaginez que vous ayez une interface avec une méthode de retour Task. Chaque classe implémentant cette interface doit retourner une tâche, même si elle s'exécute de manière synchrone (en espérant utiliser Task.FromResult). Vous pouvez bien sûr avoir deux méthodes différentes sur l'interface, une synchrone et une asynchrone, mais cela nécessite deux implémentations différentes pour éviter "sync over async" et "async over sync".
Il vous permet donc d'écrire une méthode qui est soit asynchrone, soit synchrone, plutôt que d'écrire une méthode identique pour chacune. Vous pouvez l'utiliser partout où vous utilisez Task<T>
mais cela n'apporterait souvent rien de plus.
Eh bien, cela ajoute une chose : cela ajoute une promesse implicite à l'appelant que la méthode utilise réellement la fonctionnalité supplémentaire que l'on a ajoutée. ValueTask<T>
fournit. Personnellement, je préfère choisir des types de paramètres et de retours qui en disent le plus possible à l'appelant. Ne renvoyez pas IList<T>
si l'énumération ne peut pas fournir un compte ; ne pas retourner IEnumerable<T>
si elle le peut. Vos consommateurs ne devraient pas avoir à consulter de documentation pour savoir quelles méthodes peuvent raisonnablement être appelées de manière synchrone et lesquelles ne le peuvent pas.
Je ne vois pas dans les futurs changements de conception un argument convaincant. C'est plutôt le contraire : Si une méthode change sa sémantique, cela signifie que devrait interrompre la construction jusqu'à ce que tous les appels soient mis à jour en conséquence. Si cela est considéré comme indésirable (et croyez-moi, je suis sensible au désir de ne pas casser le build), envisagez le versioning de l'interface.
C'est essentiellement à cela que sert le typage fort.
Si certains des programmeurs qui conçoivent des méthodes asynchrones dans votre atelier ne sont pas en mesure de prendre des décisions éclairées, il peut être utile d'assigner un mentor senior à chacun de ces programmeurs moins expérimentés et d'organiser une revue de code hebdomadaire. S'ils se trompent, expliquez-leur pourquoi il faut procéder différemment. C'est une surcharge pour les seniors, mais cela permettra aux juniors de se mettre à niveau beaucoup plus rapidement que si vous les jetiez dans le grand bain en leur donnant une règle arbitraire à suivre.
Si le gars qui a écrit la méthode ne sait pas si elle peut être appelée de manière synchrone, qui sur terre le fait ? !
Si vous avez autant de programmeurs inexpérimentés qui écrivent des méthodes asynchrones, ces mêmes personnes les appellent-elles aussi ? Sont-elles qualifiées pour déterminer par elles-mêmes celles qui sont sûres d'être appelées asynchrones, ou vont-elles commencer à appliquer une règle tout aussi arbitraire à la façon dont elles appellent ces choses ?
Le problème ici n'est pas vos types de retour, c'est que les programmeurs sont mis dans des rôles pour lesquels ils ne sont pas prêts. Cela a dû se produire pour une raison, donc je suis sûr que cela ne peut pas être trivial à réparer. Le décrire n'est certainement pas une solution. Mais chercher un moyen de faire passer le problème en douce au compilateur n'est pas non plus une solution.
13 votes
Je soupçonne que ça a à voir avec les avantages de
ValueTask<T>
(en termes d'allocations) ne se matérialisant pas pour les opérations qui sont en fait asynchrone (car dans ce casValueTask<T>
aura toujours besoin d'une allocation de tas). Il y a aussi la question deTask<T>
ayant beaucoup d'autres soutiens au sein des bibliothèques.0 votes
Je pensais que les arbitres étaient moins chers. Voir cette question sur le c++ stackoverflow.com/q/42721683/5976576 . Je suis peut-être en train de manquer le bateau.
4 votes
@JonSkeet les bibliothèques existantes sont un problème mais cela pose la question de savoir si Task aurait dû être ValueTask dès le départ ? Les avantages peuvent ne pas exister lorsqu'on l'utilise pour des trucs asynchrones réels, mais est-ce nuisible ?
0 votes
@Stilgar : Cela demande probablement plus de compréhension que j'en ai actuellement :) (Vu que c'est un syndicat discriminé, c'est un peu plus cher à faire passer...).
9 votes
Ver github.com/dotnet/corefx/issues/4708#issuecomment-160658188 pour plus de sagesse que je ne saurais transmettre :)
0 votes
ValueTask<T>
pourrait aider avec les API asynchrones bavardes, voir channel9.msdn.com/Series/Trois-Formes-Essentielles-pour-Async/ . Je suppose qu'il faut mesurer le parfum avant de l'adopter.1 votes
Je vous recommande également de jeter un coup d'œil à cette courte vidéo sur YouTube. Comprendre comment utiliser Task et ValueTask sur le canal Microsoft Developers.
2 votes
Préférer ValueTask à Task, toujours
3 votes
@JoelMueller l'intrigue s'épaissit :)
11 votes
Vous savez qu'il s'agit d'une question importante lorsque Jon Skeet, les deux Stephens (Cleary et Toub) et Eric Lippert apportent tous des contributions précieuses...