L’utilisation de classes Tâches ou Tâches présente un goulot d’étranglement de performance que nous n’avons pas mentionné dans les articles précédents. En résumé, ces cours mènent lorsque les résultats sont immédiatement disponiblesAllocation inutile。 Cela signifie qu’une nouvelle tâche ou un nouvel objet de tâche sera toujours créé même si le résultat est déjà disponible. Nous avons mentionné que le concept asyncre/await utilisé dans les articles précédents existe depuis la sortie de .NET 4.5. Cette fonctionnalité a été améliorée depuis C#7 avec la version .NET 4.7 avec la structure ValueTask, qui peut être utilisée comme retour pour des fonctions asynchrones.
Structure ValueTask
La structure ValueTask est apparue pour la première fois dans le dépôt corefxlab en 2015. Ce dépôt est utilisé pour expérimenter et explorer de nouvelles idées qui peuvent ou non parvenir au dépôt principal corefx. Le dépôt Corefx est le dépôt où se trouvent toutes les bibliothèques de base .NET Core. Il a été développé et suggéré par Stephen Taub pour la bibliothèque System.Threading.Tasks.Channels. À ce moment-là, Stephen a donné une brève explication :
Adresse de la bibliothèque Corefxlab :La connexion hyperlientérée est visible.
Une ValueTask est une union distincte d’un T et d’une Task, permettant à ReadAsync d’allouer librement pour retourner de manière synchrone ses valeurs T disponibles (contrairement à l’utilisation de Task.FromResult, qui nécessite l’allocation d’une instance Task). ValueTask est attendable, donc la consommation de la plupart des instances est indiscernable de la consommation des tâches. Beaucoup de gens voient les avantages d’utiliser cette structure, qui est incluse dans C#7 dans le cadre du paquet NuGet System.Threading.Tasks.Extensions. Alors, avant d’entrer dans la structure ValueTask, examinons le problème qu’elle utilise pour résoudre. Puisque Task(Task) est un type de référence, on commence parLa méthode asynchrone qui retourne l’objet Tâche signifie qu’il est alloué sur le tas à chaque fois。 Cela est nécessaire dans de nombreux cas.
Cependant, dans certains cas, les méthodes asynchrones retournent les résultats immédiatement ou se complètent de façon synchrone. Dans ces cas, cette allocation est inutile et peut devenir coûteuse dans les parties critiques du code en termes de performance. Jusqu’à la sortie de .NET 4.7, il n’y avait aucun moyen d’éviter cela, car les méthodes asynchrones devaient retourner Tâche, Tâche <T>ou void (cette dernière option étant généralement indésirable). Dans cette version de .NET, cela est étendu, ce qui signifie qu’une méthode asynchrone peut retourner n’importe quel type tant qu’elle dispose d’une méthode GetAwaiter accessible. ValueTask est un exemple concret de ce type, et il a également été ajouté à cette version.
Vous pouvez parcourir le dépôt corefx et voir l’implémentation complète de ValueTask, voici la section API qui nous intéresse :
En tant que structure, ValueTask permet d’écrire des méthodes asynchrones qui n’allouent pas de mémoire pendant l’exécution synchrone. La cohérence de l’API du concept async/await n’est pas compromise de cette manière. De plus, cette structure attend toute seule, ce qui la rend facile à utiliser. Par exemple, si nous exécutons ce code simple :
Dans la méthode MultiplyAsync, nous simulons une situation où nous voulons éviter d’utiliser Tâche et ne retourner qu’un entier simple. Cela se fait dans l’instruction if de la méthode, où nous vérifions essentiellement si le paramètre passé est nul. Le problème est le suivantMême si notre condition dans l’instruction if est vraie, le code ci-dessus crée un objet Task。 Nous résolvons ce problème ainsi :
ValueTask et Task
Comme mentionné précédemment, il y a deux avantages principaux à utiliser ValueTask :
- Améliorations de performance
- Augmenter la flexibilité de la mise en œuvre
Alors, quels sont les chiffres derrière ces améliorations de performance ? Observez ce code :
Si nous exécutons ce code, il faut 120ns pour exécuter le JIT. Maintenant, si nous remplaçons Tâche par ValueTask comme ceci :
Avec JIT, nous aurons un temps d’exécution de 65 ns. Il est vrai qu’à cause de Task.Delay, nous n’exécutons pas de manière synchrone, mais nous constatons une amélioration du temps d’exécution.
Un autre avantage que nous avons mentionné est la flexibilité accrue dans la mise en œuvre. Alors, qu’est-ce que cela signifie exactement ? Eh bien, les implémentations d’interfaces asynchrones qui devraient être synchronisées seront contraintes d’utiliser Task.Run ou Task.FromResult. Bien sûr, cela conduit aux problèmes de performance dont nous avons parlé plus tôt. Lorsque nous utilisons ValueTask, nous sommes plus susceptibles de choisir entre des implémentations synchrones ou asynchrones. Gardez à l’esprit que cela pourrait être un signe que votre code pourrait ne pas être bien conçu si cela vous arrive.
Par exemple, observez cette interface :
Disons que vous voulez l’appeler à partir d’un code comme ceci :
Parce que nous utilisons ValueTask dans l’interface, l’implémentation de l’interface peut être synchrone ou asynchrone. Nous pouvons obtenir cet avantage en évitant essentiellement d’ajouter certaines fonctions à IT qui gèrent le comportement de synchronisation. C’est beaucoup plus facile d’utiliser cette interface de cette façon. Voici une implémentation synchrone de l’interface ci-dessus :
Voici une implémentation asynchrone de la même interface :
Cependant, nous devons prendre en compte certains compromis avant d’utiliser ValueTask. Il est facile de penser que ValueTask devrait être utilisé par défaut à la place de Task, ce qui n’est certainement pas le cas. Par exemple, bien que ValueTask nous aide à éviter les affectations inutiles lorsque la synchronisation des résultats est disponible, il contient également deux champs.
Il est important de se rappeler que c’est la structure que nous utilisons ici, ce qui signifie que nous utilisons les types de valeur et tous leurs fardeaux. Task, en revanche, est un type de référence avec un seul champ.Lorsque vous utilisez ValueTask, nous avons plus de données à traiter et à traiter. Si une telle méthode est attendue dans une méthode asynchrone, alors la méthode asynchrone estLa machine à états sera également plus grande, car stocker l’ensemble de la structure nécessite généralement plus d’espace que de stocker une seule référence.
C’est pourquoi les gens de Microsoft recommandent en fait d’utiliser Tâche ou Tâche comme type de retour par défaut pour les méthodes asynchrones. Ce n’est qu’après analyse de performance que vous devriez envisager de passer à ValueTask.
résumé
ValueTask est une structure introduite dans .NET 4.7 qui nous offre de nombreuses possibilités d’utiliser des méthodes asynchrones dans .NET. Cependant, ce n’est pas sans prix. Cela est utile pour les méthodes critiques en performance exécutées de manière synchrone. Avec eux, nous pouvons éviter d’assigner des objets inutiles. Cependant, en tant que type de valeur, il comporte tous les problèmes que les types de valeur rencontrent habituellement. Par conséquent, nous pouvons bénéficier de cette structure, mais nous devons être prudents.
Adresse originale :La connexion hyperlientérée est visible.
|