À partir de C # 7.0, les méthodes asynchrones peuvent renvoyer ValueTask <T>. L'explication dit qu'il devrait être utilisé lorsque nous avons un résultat mis en cache ou que nous simulons une asynchrone via un code synchrone. Cependant, je ne comprends toujours pas quel est le problème avec l'utilisation de ValueTask toujours ou en fait pourquoi async / await n'a pas été construit avec un type valeur depuis le début. Quand ValueTask échouerait-il à faire le travail?
c#
asynchronous
Stilgar
la source
la source
ValueTask<T>
(en termes d'allocations) de ne pas se matérialiser pour des opérations qui sont réellement asynchrones (car dans ce cas, ilValueTask<T>
faudra toujours une allocation de tas). Il y a aussi la question d'Task<T>
avoir beaucoup d'autres supports au sein des bibliothèques.Réponses:
À partir de la documentation de l'API (souligné par nous)
la source
Task
allocation unique (qui est petite et bon marché de nos jours), mais au prix de rendre l' allocation existante de l' appelant plus grande et de doubler la taille de la valeur de retour (impact sur l'allocation du registre). Bien qu'il s'agisse d'un choix clair pour un scénario de lecture tamponné, je ne recommande pas de l'appliquer par défaut à toutes les interfaces.Task
ouValueTask
peut être utilisé comme type de retour synchrone (avecTask.FromResult
). Mais il y a toujours de la valeur (heh)ValueTask
si vous vous attendez à ce que quelque chose soit synchrone.ReadByteAsync
étant un exemple classique. Je crois qu'il aValueTask
été créé principalement pour les nouveaux «canaux» (flux d'octets de bas niveau), probablement également utilisés dans le noyau ASP.NET où les performances comptent vraiment.Task<T>
par défaut. Ceci est uniquement dû au fait que la plupart des développeurs ne sont pas familiarisés avec les restrictionsValueTask<T>
(en particulier, la règle «une seule consommation» et la règle «pas de blocage»). Cela dit, si tous les développeurs de votre équipe sont bons avecValueTask<T>
, alors je recommanderais une directive de préférence au niveau de l'équipeValueTask<T>
.Les types Struct ne sont pas gratuits. La copie de structures plus grandes que la taille d'une référence peut être plus lente que la copie d'une référence. Le stockage de structures plus volumineuses qu'une référence prend plus de mémoire que le stockage d'une référence. Les structures supérieures à 64 bits peuvent ne pas être enregistrées lorsqu'une référence peut être enregistrée. Les avantages d'une pression de collecte plus faible ne peuvent pas dépasser les coûts.
Les problèmes de performance doivent être abordés avec une discipline d'ingénierie. Fixez-vous des objectifs, mesurez vos progrès par rapport aux objectifs, puis décidez comment modifier le programme si les objectifs ne sont pas atteints, en mesurant en cours de route pour vous assurer que vos changements sont réellement des améliorations.
await
a été ajouté à C # longtemps après que leTask<T>
type existe déjà. Il aurait été quelque peu pervers d'inventer un nouveau type alors qu'il en existait déjà un. Etawait
a traversé de nombreuses itérations de conception avant de se fixer sur celui qui a été expédié en 2012. Le parfait est l'ennemi du bien; mieux vaut expédier une solution qui fonctionne bien avec l'infrastructure existante, puis s'il y a une demande des utilisateurs, apporter des améliorations plus tard.Je note également que la nouvelle fonctionnalité permettant aux types fournis par l'utilisateur d'être la sortie d'une méthode générée par le compilateur ajoute un risque et une charge de test considérables. Lorsque les seules choses que vous pouvez retourner sont nulles ou une tâche, l'équipe de test n'a pas à envisager de scénario dans lequel un type absolument fou est retourné. Tester un compilateur signifie déterminer non seulement les programmes que les gens sont susceptibles d'écrire, mais aussi les programmes qu'il est possible d'écrire, car nous voulons que le compilateur compile tous les programmes légaux, pas seulement tous les programmes sensés. C'est cher.
Le but de la chose est d'améliorer les performances. Il ne fait pas le travail s'il n'améliore pas de manière mesurable et significative les performances. Il n'y a aucune garantie que ce sera le cas.
la source
ValueTask<T>
n'est pas un sous-ensemble deTask<T>
, c'est un sur-ensemble .Ainsi, il vous permet d'écrire une méthode soit asynchrone ou synchrone, plutôt que d'écrire une méthode par ailleurs identique pour chacune. Vous pouvez l'utiliser partout où vous l'utilisez,
Task<T>
mais cela n'ajoute souvent rien.Eh bien, cela ajoute une chose: cela ajoute une promesse implicite à l'appelant que la méthode utilise réellement la fonctionnalité supplémentaire
ValueTask<T>
fournie. Personnellement, je préfère choisir des types de paramètres et de retours qui en disent le plus possible à l'appelant. Ne revenez pasIList<T>
si l'énumération ne peut pas fournir de décompte; ne revenez pasIEnumerable<T>
si c'est possible. Vos consommateurs ne devraient pas avoir à consulter de documentation pour savoir laquelle de vos méthodes peut raisonnablement être appelée de manière synchrone et laquelle ne le peut pas.Je ne vois pas les futurs changements de conception comme un argument convaincant. Bien au contraire: si une méthode change sa sémantique, elle doit interrompre la compilation jusqu'à ce que tous les appels à celle-ci 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 la construction), envisagez la gestion des versions 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 boutique ne sont pas en mesure de prendre des décisions éclairées, il peut être utile d'affecter un mentor senior à chacun de ces programmeurs moins expérimentés et de faire une révision hebdomadaire du code. S'ils se trompent, expliquez pourquoi cela devrait être fait différemment. C'est une surcharge pour les seniors, mais cela amènera les juniors à la vitesse beaucoup plus rapidement que de simplement les jeter dans le grand bain et de leur donner une règle arbitraire à suivre.
Si le type 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 également? Sont-ils qualifiés pour déterminer eux-mêmes lesquels sont sûrs d'appeler async, ou vont-ils commencer à appliquer une règle tout aussi arbitraire à la façon dont ils appellent ces choses?
Le problème ici n'est pas vos types de retour, ce sont les programmeurs qui sont placés dans des rôles pour lesquels ils ne sont pas prêts. Cela a dû arriver pour une raison, donc je suis sûr que cela ne peut pas être trivial à réparer. Décrire ce n'est certainement pas une solution. Mais chercher un moyen de faire passer le problème au-delà du compilateur n'est pas non plus une solution.
la source
ValueTask<T>
, je suppose que le gars qui a écrit la méthode l'a fait parce que la méthode utilise en fait la fonctionnalité quiValueTask<T>
ajoute. Je ne comprends pas pourquoi vous pensez qu'il est souhaitable que toutes vos méthodes aient le même type de retour. Quel est le but là-bas?object
car peut-être qu'un jour vous voudrez qu'elles reviennentint
au lieu destring
.Il y a quelques changements dans .Net Core 2.1 . À partir de .net core 2.1 ValueTask peut représenter non seulement les actions terminées synchrones mais aussi l'asynchrone terminée. De plus, nous recevons du type non générique
ValueTask
.Je laisserai le commentaire de Stephen Toub qui est lié à votre question:
La fonctionnalité peut être utilisée non seulement dans le .net core 2.1. Vous pourrez l'utiliser avec le package System.Threading.Tasks.Extensions .
la source