Comment utiliser ValueTask en C #

La programmation asynchrone est utilisée depuis un certain temps maintenant. Ces dernières années, il a été rendu plus puissant avec l'introduction des mots-clés async et await. Vous pouvez tirer parti de la programmation asynchrone pour augmenter la réactivité et le débit de votre application.

Le type de retour recommandé d'une méthode asynchrone en C # est Task. Vous devez renvoyer Task si vous souhaitez écrire une méthode asynchrone qui renvoie une valeur. Si vous souhaitez écrire un gestionnaire d'événements, vous pouvez renvoyer void à la place. Jusqu'à C # 7.0, une méthode asynchrone pouvait renvoyer Task, Task ou void. À partir de C # 7.0, une méthode asynchrone peut également renvoyer ValueTask (disponible dans le cadre du package System.Threading.Tasks.Extensions) ou ValueTask. Cet article présente une discussion sur la façon dont nous pouvons travailler avec ValueTask en C #.

Pour utiliser les exemples de code fournis dans cet article, vous devez disposer de Visual Studio 2019 installé sur votre système. Si vous n'en avez pas déjà une copie, vous pouvez télécharger Visual Studio 2019 ici.

Créer un projet d'application console .NET Core dans Visual Studio

Tout d'abord, créons un projet d'application console .NET Core dans Visual Studio. En supposant que Visual Studio 2019 est installé sur votre système, suivez les étapes décrites ci-dessous pour créer un nouveau projet d'application console .NET Core dans Visual Studio.

  1. Lancez l'IDE de Visual Studio.
  2. Cliquez sur "Créer un nouveau projet".
  3. Dans la fenêtre «Créer un nouveau projet», sélectionnez «Application console (.NET Core)» dans la liste des modèles affichés.
  4. Cliquez sur Suivant.
  5. Dans la fenêtre «Configurer votre nouveau projet» ci-dessous, spécifiez le nom et l'emplacement du nouveau projet.
  6. Cliquez sur Créer.

Cela créera un nouveau projet d'application console .NET Core dans Visual Studio 2019. Nous utiliserons ce projet pour illustrer l'utilisation de ValueTask dans les sections suivantes de cet article.

Pourquoi devrais-je utiliser ValueTask?

Une tâche représente l'état d'une opération, c'est-à-dire si l'opération est terminée, annulée, etc. Une méthode asynchrone peut renvoyer une Task ou une ValueTask.

Désormais, comme Task est un type de référence, le retour d'un objet Task à partir d'une méthode asynchrone implique l'allocation de l'objet sur le tas géré chaque fois que la méthode est appelée. Par conséquent, une mise en garde lors de l'utilisation de Task est que vous devez allouer de la mémoire dans le tas géré chaque fois que vous renvoyez un objet Task à partir de votre méthode. Si le résultat de l'opération effectuée par votre méthode est disponible immédiatement ou se termine de manière synchrone, cette allocation n'est pas nécessaire et devient donc coûteuse.

Voici exactement où ValueTask vient à la rescousse. ValueTask offre deux avantages majeurs. Premièrement, ValueTask améliore les performances car il n'a pas besoin d'allocation de tas, et deuxièmement, il est à la fois facile et flexible à implémenter. En renvoyant ValueTask au lieu de Task à partir d'une méthode asynchrone lorsque le résultat est immédiatement disponible, vous pouvez éviter la surcharge inutile d'allocation puisque «T» représente ici une structure et une structure en C # est un type valeur (contrairement au «T» dans Task, qui représente une classe).

Task et ValueTask représentent deux principaux types «attendables» en C #. Notez que vous ne pouvez pas bloquer sur une ValueTask. Si vous avez besoin de bloquer, vous devez convertir la ValueTask en Task à l'aide de la méthode AsTask, puis bloquer sur cet objet Task de référence.

Notez également que chaque ValueTask ne peut être utilisée qu'une seule fois. Ici, le mot «consommer» implique qu'une ValueTask peut attendre de manière asynchrone (attendre) que l'opération se termine ou tirer parti d'AsTask pour convertir une ValueTask en Task. Cependant, une ValueTask ne doit être consommée qu'une seule fois, après quoi la ValueTask doit être ignorée.

Exemple ValueTask en C #

Supposons que vous ayez une méthode asynchrone qui renvoie une tâche. Vous pouvez profiter de Task.FromResult pour créer l'objet Task comme indiqué dans l'extrait de code ci-dessous.

Tâche publique GetCustomerIdAsync ()

{

    return Task.FromResult (1);

}

L'extrait de code ci-dessus ne crée pas la magie de la machine d'état asynchrone entière, mais il alloue un objet Task dans le tas managé. Pour éviter cette allocation, vous souhaiterez peut-être profiter d'une ValueTask à la place, comme indiqué dans l'extrait de code ci-dessous.

Public ValueTask GetCustomerIdAsync ()

{

    retourne une nouvelle ValueTask (1);

}

L'extrait de code suivant illustre une implémentation synchrone de ValueTask.

 interface publique IRepository

    {

        ValueTask GetData ();

    }

La classe Repository étend l'interface IRepository et implémente ses méthodes comme indiqué ci-dessous.

    référentiel de classe publique: IRepository

    {

        Public ValueTask GetData ()

        {

            valeur var = par défaut (T);

            retourne une nouvelle ValueTask (valeur);

        }

    }

Voici comment vous pouvez appeler la méthode GetData à partir de la méthode Main.

static void Main (string [] args)

        {

            IRepository repository = nouveau Repository ();

            var result = repository.GetData ();

            if (result.IsCompleted)

                 Console.WriteLine ("Opération terminée ...");

            autre

                Console.WriteLine ("Opération incomplète ...");

            Console.ReadKey ();

        }

Ajoutons maintenant une autre méthode à notre référentiel, cette fois une méthode asynchrone nommée GetDataAsync. Voici à quoi ressemblerait l'interface IRepository modifiée.

interface publique IRepository

    {

        ValueTask GetData ();

        ValueTask GetDataAsync ();

    }

La méthode GetDataAsync est implémentée par la classe Repository comme indiqué dans l'extrait de code ci-dessous.

    référentiel de classe publique: IRepository

    {

        Public ValueTask GetData ()

        {

            valeur var = par défaut (T);

            retourne une nouvelle ValueTask (valeur);

        }

        public async ValueTask GetDataAsync ()

        {

            valeur var = par défaut (T);

            attendre Task.Delay (100);

            valeur de retour;

        }

    }

Quand dois-je utiliser ValueTask en C #?

Malgré les avantages fournis par ValueTask, il existe certains compromis à utiliser ValueTask au lieu de Task. ValueTask est un type de valeur avec deux champs, tandis que Task est un type de référence avec un seul champ. Par conséquent, utiliser une ValueTask signifie travailler avec plus de données, car un appel de méthode renverrait deux champs de données au lieu d'un. De plus, si vous attendez une méthode qui renvoie une ValueTask, la machine à états de cette méthode asynchrone serait également plus grande - car elle devrait accueillir une structure contenant deux champs au lieu d'une seule référence dans le cas d'une Task.

De plus, si le consommateur d'une méthode asynchrone utilise Task.WhenAll ou Task.WhenAny, l'utilisation de ValueTask comme type de retour dans une méthode asynchrone peut devenir coûteuse. En effet, vous auriez besoin de convertir la ValueTask en Task à l'aide de la méthode AsTask, ce qui entraînerait une allocation qui pourrait être facilement évitée si une tâche mise en cache avait été utilisée en premier lieu.

Voici la règle du pouce. Utilisez Task lorsque vous avez un morceau de code qui sera toujours asynchrone, c'est-à-dire lorsque l'opération ne se terminera pas immédiatement. Tirez parti de ValueTask lorsque le résultat d'une opération asynchrone est déjà disponible ou lorsque vous avez déjà un résultat mis en cache. Dans tous les cas, vous devez effectuer l'analyse de performances nécessaire avant d'envisager ValueTask.

Comment faire plus en C #:

  • Comment utiliser l'immuabilité en C
  • Comment utiliser const, readonly et static en C #
  • Comment utiliser les annotations de données en C #
  • Comment travailler avec les GUID en C # 8
  • Quand utiliser une classe abstraite ou une interface en C #
  • Comment travailler avec AutoMapper en C #
  • Comment utiliser les expressions lambda en C #
  • Comment travailler avec les délégués Action, Func et Predicate en C #
  • Comment travailler avec des délégués en C #
  • Comment implémenter un simple logger en C #
  • Comment travailler avec des attributs en C #
  • Comment travailler avec log4net en C #
  • Comment implémenter le modèle de conception de référentiel en C #
  • Comment travailler avec la réflexion en C #
  • Comment travailler avec Filesystemwatcher en C #
  • Comment effectuer une initialisation différée en C #
  • Comment travailler avec MSMQ en C #
  • Comment travailler avec des méthodes d'extension en C #
  • Comment utiliser les expressions lambda en C #
  • Quand utiliser le mot clé volatile en C #
  • Comment utiliser le mot clé yield en C #
  • Comment implémenter le polymorphisme en C #
  • Comment créer votre propre planificateur de tâches en C #
  • Comment travailler avec RabbitMQ en C #
  • Comment travailler avec un tuple en C #
  • Explorer les méthodes virtuelles et abstraites en C #
  • Comment utiliser l'ORM Dapper en C #
  • Comment utiliser le modèle de conception de poids mouche en C #