Fermer

janvier 10, 2023

Faire un petit-déjeuner asynchrone dans .NET

Faire un petit-déjeuner asynchrone dans .NET


Découvrez dans cet article de blog comment la programmation asynchrone fonctionne dans .NET en créant un petit-déjeuner efficace.

L’utilisation massive de la technologie a exigé une charge de travail élevée de la part des systèmes modernes. Après tout, il existe des scénarios dans lesquels une seule application doit exécuter des milliers de transactions en peu de temps. Pour que cela soit possible, certaines techniques doivent être mises en pratique, l’une des plus utilisées est la programmation asynchrone.

La programmation asynchrone est un sujet que de nombreux développeurs utilisent mais ne savent souvent pas exactement comment cela fonctionne – après tout, ce n’est pas si simple à comprendre.

Dans cet article, le petit déjeuner sera utilisé pour démontrer comment la programmation asynchrone fonctionne dans .NET et quels sont ses avantages par rapport à la programmation synchrone.

Dans un premier temps, nous verrons une introduction au sujet, puis nous créerons deux applications console en .NET pour voir en pratique comment utiliser les fonctionnalités .NET pour la programmation asynchrone.

Comprendre le concept de programmation asynchrone

Il est très probable que vous, en tant que développeur, ayez déjà rencontré des situations où le programme devait effectuer une action simple telle que valider les données d’un utilisateur ou extraire des caractères alphanumériques d’une certaine valeur.

Habituellement, les activités de ce type prennent peu de temps et il n’y a aucun problème que chaque tâche attende la fin de la tâche précédente. Dans ce cas, nous utilisons la « programmation synchrone ».

Cependant, il existe des scénarios où l’exécution de certaines tâches peut prendre un temps considérable, comme l’appel d’une API externe ou d’une procédure dans la base de données. Pour que les autres tâches n’aient pas à attendre tout ce temps pour être exécutées, la meilleure option est d’utiliser la « programmation asynchrone », où chaque tâche fonctionne indépendamment et est orchestrée par le cœur du système lui-même.

Programmation asynchrone dans .NET

.NET prend en charge la programmation asynchrone via le Modèle de programmation asynchrone des tâches (TAP). Cette fonctionnalité native vous permet d’éviter les goulots d’étranglement de performances et d’améliorer la réactivité d’une application grâce à une approche rationalisée qui exploite la prise en charge asynchrone dans .NET Framework 4.5 et supérieur, .NET Core et Windows Runtime.

TAP a tous les avantages de la programmation asynchrone avec un minimum d’effort.

Synchrone vs asynchrone

Pour voir en pratique les avantages d’une application asynchrone, nous allons la comparer avec la même application mais réalisée de manière synchrone. Ainsi, comme premier exemple, nous allons créer une application console simple qui simule la préparation du petit-déjeuner de manière synchrone.

Création de l’application synchrone

Notre petit-déjeuner synchrone suivra cet ordre :

  1. Remplir une tasse de café
  2. Faire frire deux oeufs
  3. Faire revenir trois tranches de bacon
  4. Remplir un verre de jus d’orange

Comme nous travaillons avec la synchronisation, chaque tâche ne commencera que lorsque la tâche précédente se terminera, c’est-à-dire que l’ordre doit être maintenu, de sorte que le processus ne se terminera que lorsque le jus d’orange sera dans le verre.

Vous pouvez accéder au code source complet du projet à ce lien : code source.

Pour créer l’application, vous pouvez suivre les étapes ci-dessous. (Remarque : ce message a été écrit avec .NET 6, mais .NET 7 est maintenant disponible!)

Dans Visual Studio :

  • Sélectionnez Créer un nouveau projet
  • Sélectionnez l’application console
  • Nommez le projet (SyncBreakfast est ma suggestion)

Par borne :

dotnet new console --framework net6.0 -n SyncBreakfast

Ensuite, ouvrez le projet avec votre IDE préféré et créez un nouveau dossier appelé « Modèles » et à l’intérieur, il crée la classe ci-dessous :

namespace SyncBreakfast.Models;
public class Breakfast
{
    public Coffee Coffee { get; set; } = new Coffee();
    public Bacon Bacon { get; set; } = new Bacon();
    public Egg Egg { get; set; } = new Egg();
    public Juice Juice { get; set; } = new Juice();
} 

public class Coffee
{
    public Coffee PourCoffee(int cup)
    {
        Console.WriteLine($"Pouring {cup} of coffee");
        Task.Delay(1000).Wait();
        return new Coffee();
    }
}

public class Bacon
{
    public Bacon FryBacon(int slices)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Cooking a slice of bacon");
            Task.Delay(2000).Wait();
        }
        return new Bacon();
    }
}

public class Egg
{
    public Egg FryEggs(int eggs)
    {
        for (int egg = 0; egg < eggs; egg++)
        {
            Console.WriteLine("Cooking a egg");
            Task.Delay(3000).Wait();
        }
        return new Egg();
    }
}

public class Juice
{
    public Juice PourJuice()
    {
        Console.WriteLine("Pouring orange juice");
        Task.Delay(1000).Wait();
        return new Juice();
    }
}

Notez que la classe Petit-déjeuner a quatre sous-classes : Café, Bacon, Oeuf et Jus. Chacun d’eux a une méthode pour exécuter sa tâche respective qui prend un certain temps à s’exécuter et affiche un message lorsqu’elle est prête.

Comme les méthodes s’exécutent de manière synchrone, chaque tâche s’exécute après la fin de la tâche précédente. Dans ce scénario, c’est comme si nous avions un seul chef pour préparer le petit-déjeuner, et il doit d’abord finir de mettre le café dans la tasse pour commencer à faire frire les œufs. Dans ce scénario synchrone, il ne peut pas effectuer plus d’une tâche à la fois – notre cuisinier synchrone n’est pas très efficace.

Pour vérifier comment se déroulera cette exécution, dans la classe Program, remplacez le contenu existant par le code ci-dessous :

using SyncBreakfast.Models;

var initialTime = DateTime.Now.Second;

var breakfast = new Breakfast();

var coffee = breakfast.Coffee.PourCoffee(1);
Console.WriteLine("Coffee is ready");

var bacon = breakfast.Bacon.FryBacon(2);
Console.WriteLine("Bacon is ready");

var egg = breakfast.Egg.FryEggs(2);
Console.WriteLine("Egg is ready");

var juice = breakfast.Juice.PourJuice();
Console.WriteLine("Juice is ready");

var finishTime = DateTime.Now.Second;

Console.WriteLine($"Breakfast is ready! The process took {finishTime - initialTime} seconds");

Ici, nous appelons les méthodes de préparation du petit-déjeuner et le temps d’exécution de l’ensemble du processus en secondes est également indiqué. Lors de l’exécution du projet, nous obtenons le résultat suivant :

Application de synchronisation d'exécution

Comme nous pouvons le voir sur l’image ci-dessus, notre chef synchrone a préparé le petit déjeuner une tâche à la fois, et à la fin du processus, le temps d’exécution total était de 13 secondes.

Diagramme de flux synchrone.png

Mais combien de temps cela prendrait-il si le petit-déjeuner était préparé de manière asynchrone en considérant le même temps pour chaque tâche ?

Ensuite, nous allons implémenter le mode asynchrone de préparation du petit-déjeuner et vérifier s’il est possible d’être plus rapide que 13 secondes.

Création de l’application asynchrone

De la même manière que précédemment, pour créer l’application asynchrone, vous pouvez suivre les étapes ci-dessous :

Dans Visual Studio :

  • Sélectionnez Créer un nouveau projet
  • Sélectionnez l’application console
  • Nommez le projet (AsyncBreakfast est ma suggestion)

Par borne :

dotnet new console --framework net6.0 -n AsyncBreakfast

Ensuite, créez un nouveau dossier appelé « Modèles » et à l’intérieur, créez la classe ci-dessous :

namespace AsyncBreakfast.Models;
public class Breakfast
{
    public Coffee Coffee { get; set; } = new Coffee();
    public Bacon Bacon { get; set; } = new Bacon();
    public Egg Egg { get; set; } = new Egg();
    public Juice Juice { get; set; } = new Juice();
}

public class Coffee
{
    public async Task<Coffee> PourCoffee(int cup)
    {
        Console.WriteLine($"Pouring {cup} of coffee");
        await Task.Delay(1000);
        return new Coffee();
    }
}

public class Bacon
{
    public async Task<Bacon> FryBacon(int slices)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Cooking a slice of bacon");
            await Task.Delay(2000);
        }
        return new Bacon();
    }
}

public class Egg
{
    public async Task<Egg> FryEggs(int eggs)
    {
        for (int egg = 0; egg < eggs; egg++)
        {
            Console.WriteLine("Cooking a egg");
            await Task.Delay(millisecondsDelay: 3000);
        }
        return new Egg();
    }
}

public class Juice
{
    public async Task<Juice> PourJuice()
    {
        Console.WriteLine("Pouring orange juice");
        await Task.Delay(1000);
        return new Juice();
    }
}

Notez que la classe de modèle au format asynchrone est très similaire à celle que nous avons créée précédemment dans l’application synchrone. Néanmoins, nous utilisons ici les fonctionnalités disponibles dans .NET pour créer des méthodes asynchrones via le mot-clé async et renvoyer un objet Task qui représente une opération unique, ne renvoie pas de valeur et est généralement effectuée de manière asynchrone. Vous pouvez en savoir plus sur ce type de données ici : Définition de classe de tâche.

Nous devons maintenant créer la méthode qui utilisera la classe de modèle, alors remplacez le code dans le fichier Program.cs par :

using AsyncBreakfast.Models;

var initialTime = DateTime.Now.Second;

await BreakfastProcess();

async Task BreakfastProcess()
{
    var breakfast = new Breakfast();
    var coffeeTask = breakfast.Coffee.PourCoffee(1);
    var baconTask = breakfast.Bacon.FryBacon(2);
    var eggTask = breakfast.Egg.FryEggs(2);
    var juiceTask = breakfast.Juice.PourJuice();

    var breakfastTasks = new List<Task> { coffeeTask, baconTask, eggTask, juiceTask };

    while (breakfastTasks.Count > 0)
    {
        Task finishedTask = await Task.WhenAny(breakfastTasks);

        if (finishedTask == coffeeTask)
        {
            Console.WriteLine("Coffee are ready");
        }
        else if (finishedTask == baconTask)
        {
            Console.WriteLine("Bacon is ready");
        }
        else if (finishedTask == eggTask)
        {
            Console.WriteLine("Egg is ready");
        }
        else if (finishedTask == juiceTask)
        {
            Console.WriteLine("Juice is ready");
        }
        breakfastTasks.Remove(finishedTask);
    }
}

var finishTime = DateTime.Now.Second;
Console.WriteLine($"Breakfast is ready! The process took {finishTime - initialTime} seconds");

Dans le code ci-dessus, nous créons une méthode asynchrone appelée « BreakfastProcess » et à l’intérieur, nous attribuons le retour de chaque méthode à une variable. Ensuite, nous ajoutons les tâches renvoyées à une liste. Cette liste est à son tour parcourue et un message s’affiche en fonction de la tâche en cours de traitement.

Notez que nous utilisons la méthode native .NET « WhenAny » et qu’elle crée une tâche qui se terminera lorsque l’une des tâches données sera terminée. À la fin du processus, l’heure d’achèvement du processus de message s’affiche.

Ainsi, contrairement à l’approche précédente, il n’est plus nécessaire d’attendre la fin d’une tâche pour en commencer une autre. Après tout, grâce à la méthode asynchrone, tout sera exécuté simultanément et le message d’achèvement s’affichera au fur et à mesure que les tâches seront terminées.

Si nous exécutons le programme, nous obtiendrons le résultat suivant :

Application asynchrone d'exécution

Comme on peut le voir dans l’image ci-dessus, lors de l’exécution de l’application de manière asynchrone, les tâches sont indépendantes, elles sont donc exécutées sans qu’il soit nécessaire que la tâche précédente soit terminée. Avec cela, le temps d’exécution a diminué de moitié, c’est-à-dire que le mode synchrone a mis 13 secondes pour terminer l’exécution, tandis que le mode asynchrone n’a pris que 6 secondes.

Flowshart asynchrone.png

Conclusion

Comme indiqué dans l’article, .NET dispose de ressources natives pour travailler avec des méthodes asynchrones, de sorte que le développeur n’a pas à se soucier de trop de détails et peut se concentrer sur la meilleure façon d’utiliser ces ressources.

Un point important à considérer est que bien que les méthodes asynchrones soient plus efficaces, il faut toujours tenir compte du moment où leur utilisation est vraiment nécessaire. Dans des scénarios simples, où il n’y a pas de temps expressif, la meilleure option est peut-être les méthodes synchrones, mais sinon, il est valable d’évaluer l’utilisation de la programmation asynchrone.




Source link

janvier 10, 2023