Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Le modèle de programmation asynchrone de tâche (TAP) fournit une couche d’abstraction par rapport au codage asynchrone classique. Dans ce modèle, vous écrivez du code sous la forme d’une séquence d’instructions, comme d’habitude. La différence est que vous pouvez lire votre code basé sur des tâches en tant que compilateur traite chaque instruction et avant de commencer à traiter l’instruction suivante. Pour accomplir ce modèle, le compilateur effectue de nombreuses transformations pour effectuer chaque tâche. Certaines instructions peuvent lancer le travail et retourner un Task objet qui représente le travail en cours et le compilateur doit résoudre ces transformations. L’objectif de la programmation asynchrone de tâche est d’activer le code qui lit comme une séquence d’instructions, mais s’exécute dans un ordre plus compliqué. L’exécution est basée sur l’allocation de ressources externes et lorsque les tâches sont terminées.
Le modèle de programmation asynchrone de tâche est analogue à la façon dont les personnes donnent des instructions pour les processus qui incluent des tâches asynchrones. Cet article utilise un exemple avec des instructions pour préparer le petit déjeuner afin de montrer comment les mots clés async
et await
facilitent la compréhension du code qui inclut une série d’instructions asynchrones. Les instructions relatives à la création d’un petit-déjeuner peuvent être fournies sous forme de liste :
- Versez une tasse de café.
- Chauffer une poêle, puis faire frire deux œufs.
- Faites frire trois tranches de bacon.
- Faites griller deux tranches de pain.
- Étalez le beurre et la confiture sur le toast.
- Verser un verre de jus d’orange.
Si vous avez une expérience de cuisson, vous pouvez suivre ces instructions de manière asynchrone. Vous commencez à réchauffer la casserole pour les œufs, puis commencer à frire le bacon. Vous mettez le pain dans le grille-pain, puis commencez à cuire les œufs. À chaque étape du processus, vous démarrez une tâche, puis passez à d’autres tâches prêtes pour votre attention.
La cuisson du petit déjeuner est un bon exemple de travail asynchrone qui n’est pas parallèle. Une personne (ou thread) peut gérer toutes les tâches. Une personne peut faire du petit déjeuner de manière asynchrone en démarrant la tâche suivante avant la fin de la tâche précédente. Chaque tâche de cuisson progresse, qu'une personne surveille activement le processus ou non. Dès que vous commencez à réchauffer la casserole pour les œufs, vous pouvez commencer à frire le bacon. Une fois que le bacon commence à cuire, vous pouvez placer le pain dans le grille-pain.
Pour un algorithme parallèle, vous avez besoin de plusieurs personnes qui cuisinent (ou plusieurs threads). Une personne cuisine les œufs, une autre friture le bacon, et ainsi de suite. Chaque personne se concentre sur sa tâche spécifique. Chaque personne qui cuisine (ou chaque thread) est bloquée de manière synchrone en attendant que la tâche en cours se termine : Le lard est prêt à être retourné, le pain est prêt à être mis dans le grille-pain, et ainsi de suite.
Considérez la même liste d’instructions synchrones écrites en tant qu’instructions de code C# :
using System;
using System.Threading.Tasks;
namespace AsyncBreakfast
{
// These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }
class Program
{
static void Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Egg eggs = FryEggs(2);
Console.WriteLine("eggs are ready");
Bacon bacon = FryBacon(3);
Console.WriteLine("bacon is ready");
Toast toast = ToastBread(2);
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
private static Juice PourOJ()
{
Console.WriteLine("Pouring orange juice");
return new Juice();
}
private static void ApplyJam(Toast toast) =>
Console.WriteLine("Putting jam on the toast");
private static void ApplyButter(Toast toast) =>
Console.WriteLine("Putting butter on the toast");
private static Toast ToastBread(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
Task.Delay(3000).Wait();
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
private static Bacon FryBacon(int slices)
{
Console.WriteLine($"putting {slices} slices of bacon in the pan");
Console.WriteLine("cooking first side of bacon...");
Task.Delay(3000).Wait();
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
Task.Delay(3000).Wait();
Console.WriteLine("Put bacon on plate");
return new Bacon();
}
private static Egg FryEggs(int howMany)
{
Console.WriteLine("Warming the egg pan...");
Task.Delay(3000).Wait();
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
Task.Delay(3000).Wait();
Console.WriteLine("Put eggs on plate");
return new Egg();
}
private static Coffee PourCoffee()
{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}
Si vous interprétez ces instructions comme un ordinateur, le petit déjeuner prend environ 30 minutes pour se préparer. La durée correspond à la somme des heures de tâche individuelles. L’ordinateur bloque chaque instruction jusqu’à ce que tout le travail se termine, puis passe à l’instruction de tâche suivante. Cette approche peut prendre beaucoup de temps. Dans l’exemple de petit déjeuner, la méthode informatique crée un petit déjeuner insatisfaisant. Les tâches ultérieures de la liste synchrone, telles que le toasting du pain, ne démarrent pas tant que les tâches antérieures ne sont pas terminées. Certains aliments sont froids avant que le petit déjeuner soit prêt à servir.
Si vous souhaitez que l’ordinateur exécute des instructions de manière asynchrone, vous devez écrire du code asynchrone. Lorsque vous écrivez des programmes clients, vous souhaitez que l’interface utilisateur soit réactive à l’entrée utilisateur. Votre application ne doit pas figer toutes les interactions lors du téléchargement de données à partir du web. Lorsque vous écrivez des programmes serveur, vous ne souhaitez pas bloquer les threads susceptibles de traiter d’autres demandes. L’utilisation de code synchrone lorsque des alternatives asynchrones existent nuit à votre capacité à effectuer un scale-out moins coûteux. Vous payez pour les threads bloqués.
Les applications modernes réussies nécessitent du code asynchrone. Sans prise en charge du langage, l’écriture de code asynchrone nécessite des rappels, des événements d’achèvement ou d’autres moyens qui masquent l’intention d’origine du code. L’avantage du code synchrone est l’action pas à pas qui facilite l’analyse et la compréhension. Les modèles asynchrones traditionnels vous obligent à vous concentrer sur la nature asynchrone du code, et non sur les actions fondamentales du code.
Ne bloquez pas, attendez à la place
Le code précédent met en évidence une pratique de programmation malheureuse : écriture de code synchrone pour effectuer des opérations asynchrones. Le code empêche le thread actuel d’effectuer tout autre travail. Le code n’interrompt pas le thread pendant l’exécution de tâches. Le résultat de ce modèle est similaire à regarder le grille-pain après y avoir mis le pain. Vous ignorez les interruptions et ne démarrez pas d’autres tâches tant que le pain ne s’affiche pas. Vous ne prenez pas le beurre et la confiture hors du réfrigérateur. Vous pourriez ne pas remarquer un feu qui démarre sur la cuisinière. Vous voulez à la fois griller le pain et gérer d’autres préoccupations en même temps. Il en va de même avec votre code.
Vous pouvez commencer par mettre à jour le code afin que le thread ne bloque pas pendant l’exécution des tâches. Le await
mot clé fournit un moyen non bloquant de démarrer une tâche, puis de poursuivre l’exécution une fois la tâche terminée. Une version asynchrone simple du code de petit déjeuner ressemble à l’extrait de code suivant :
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
Egg eggs = await FryEggsAsync(2);
Console.WriteLine("eggs are ready");
Bacon bacon = await FryBaconAsync(3);
Console.WriteLine("bacon is ready");
Toast toast = await ToastBreadAsync(2);
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
Le code met à jour les corps de méthode d’origine de FryEggs
, FryBacon
et ToastBread
pour retourner Task<Egg>
, Task<Bacon>
et Task<Toast>
les objets, respectivement. Les noms de méthode mis à jour incluent le suffixe « Async » : FryEggsAsync
, FryBaconAsync
et ToastBreadAsync
. La Main
méthode retourne l’objet Task
, même s’il n’a pas d’expression return
, qui est par conception. Pour plus d’informations, consultez Évaluation d’une fonction asynchrone de retour void.
Remarque
Le code mis à jour ne tire pas encore parti des fonctionnalités clés de la programmation asynchrone, ce qui peut entraîner des temps d’achèvement plus courts. Le code traite les tâches dans environ la même durée que la version synchrone initiale. Pour connaître les implémentations complètes de méthode, consultez la version finale du code plus loin dans cet article.
Nous allons appliquer l’exemple de petit déjeuner au code mis à jour. Le thread ne bloque pas pendant que les œufs ou le bacon sont cuits, mais le code ne démarre pas non plus d’autres tâches tant que le travail actuel n’est pas terminé. Vous mettez toujours le pain dans le grille-pain et regardez le grille-pain jusqu’à ce que le pain ressorte, mais vous pouvez maintenant réagir aux interruptions. Dans un restaurant où plusieurs commandes sont passées, le cuisinier peut commencer une nouvelle commande alors qu’un autre est déjà en cuisine.
Dans le code mis à jour, le thread travaillant sur le petit-déjeuner n’est pas bloqué en attendant toute tâche démarrée qui n’est pas terminée. Pour certaines applications, cette modification est tout ce dont vous avez besoin. Vous pouvez permettre à votre application de prendre en charge l’interaction utilisateur pendant les téléchargements de données à partir du web. Dans d’autres scénarios, vous pouvez démarrer d’autres tâches en attendant que la tâche précédente se termine.
Démarrer des tâches simultanément
Pour la plupart des opérations, vous souhaitez démarrer immédiatement plusieurs tâches indépendantes. À mesure que chaque tâche se termine, vous lancez d’autres travaux prêts à commencer. Lorsque vous appliquez cette méthodologie à l’exemple de petit déjeuner, vous pouvez préparer le petit déjeuner plus rapidement. Vous préparez également tout en même temps, afin de pouvoir profiter d’un petit déjeuner chaud.
La System.Threading.Tasks.Task classe et les types associés sont des classes que vous pouvez utiliser pour appliquer ce style de raisonnement aux tâches en cours. Cette approche vous permet d’écrire du code qui ressemble plus étroitement à la façon dont vous créez le petit déjeuner en vie réelle. Vous commencez à cuire les œufs, le bacon et le toast en même temps. Comme chaque élément alimentaire nécessite une action, vous faites attention à cette tâche, prenez soin de l’action, puis attendez quelque chose d’autre qui nécessite votre attention.
Dans votre code, vous démarrez une tâche et conservez l'objet Task qui représente le travail. Vous utilisez la await
méthode sur la tâche pour différer l'exécution jusqu'à ce que le résultat soit prêt.
Appliquez ces modifications au code du petit déjeuner. La première étape consiste à stocker les tâches pour les opérations au démarrage, plutôt que d’utiliser l’expression await
:
Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");
Task<Egg> eggsTask = FryEggsAsync(2);
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Task<Bacon> baconTask = FryBaconAsync(3);
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");
Task<Toast> toastTask = ToastBreadAsync(2);
Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Console.WriteLine("Breakfast is ready!");
Ces révisions n’aident pas à préparer votre petit déjeuner plus rapidement. L’expression await
est appliquée à toutes les tâches dès qu’elles démarrent. L’étape suivante consiste à déplacer les await
expressions pour le bacon et les œufs à la fin de la méthode, avant de servir le petit déjeuner :
Coffee cup = PourCoffee();
Console.WriteLine("Coffee is ready");
Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);
Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");
Console.WriteLine("Breakfast is ready!");
Vous disposez maintenant d’un petit déjeuner préparé de manière asynchrone qui prend environ 20 minutes pour préparer. Le temps de cuisson total est réduit, car certaines tâches s’exécutent simultanément.
Les mises à jour du code améliorent le processus de préparation en réduisant le temps de cuisson, mais elles entraînent une régression en brûlant les œufs et le bacon. Vous démarrez toutes les tâches asynchrones à la fois. Vous attendez chaque tâche uniquement lorsque vous avez besoin des résultats. Le code peut être similaire au programme dans une application web qui envoie des requêtes à différents microservices, puis combine les résultats dans une seule page. Vous effectuez toutes les requêtes immédiatement, puis appliquez l’expression await
sur toutes ces tâches et composez la page web.
Prise en charge de la composition avec des tâches
Les révisions de code précédentes permettent de préparer tout le petit déjeuner en même temps, à l’exception du toast. Le processus de fabrication du toast est une composition d’une opération asynchrone (toaster le pain) avec des opérations synchrones (répartir le beurre et la confiture sur le toast). Cet exemple illustre un concept important de programmation asynchrone :
Importante
La composition d’une opération asynchrone suivie d’un travail synchrone est une opération asynchrone. Indiqué d’une autre façon, si une partie d’une opération est asynchrone, l’opération entière est asynchrone.
Dans les mises à jour précédentes, vous avez appris à utiliser les objets Task ou Task<TResult> pour contenir des tâches en cours d'exécution. Vous attendez que chaque tâche soit terminée avant d'utiliser son résultat. L’étape suivante consiste à créer des méthodes qui représentent la combinaison d’autres travaux. Avant de servir le petit déjeuner, vous voulez attendre que la tâche de griller le pain soit terminée avant d'étaler le beurre et la confiture.
Vous pouvez représenter ce travail avec le code suivant :
static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);
return toast;
}
La MakeToastWithButterAndJamAsync
méthode a le async
modificateur dans sa signature qui signale au compilateur que la méthode contient une await
expression et contient des opérations asynchrones. La méthode représente la tâche qui grille le pain, puis étale le beurre et la confiture. La méthode retourne un Task<TResult> objet qui représente la composition des trois opérations.
Le bloc principal révisé de code ressemble maintenant à ceci :
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
var eggsTask = FryEggsAsync(2);
var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);
var eggs = await eggsTask;
Console.WriteLine("eggs are ready");
var bacon = await baconTask;
Console.WriteLine("bacon is ready");
var toast = await toastTask;
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
Cette modification de code illustre une technique importante pour l’utilisation du code asynchrone. Vous composez des tâches en séparant les opérations en une nouvelle méthode qui retourne une tâche. Vous pouvez choisir quand attendre cette tâche. Vous pouvez démarrer d’autres tâches simultanément.
Gérer les exceptions asynchrones
Jusqu’à ce stade, votre code suppose implicitement que toutes les tâches se terminent correctement. Les méthodes asynchrones lèvent des exceptions, tout comme leurs équivalents synchrones. Les objectifs de prise en charge asynchrone des exceptions et de la gestion des erreurs sont les mêmes que pour la prise en charge asynchrone en général. La meilleure pratique consiste à écrire du code qui lit comme une série d’instructions synchrones. Les tâches génèrent des exceptions lorsqu'elles ne peuvent pas se terminer correctement. Le code client peut intercepter ces exceptions lorsque l’expression await
est appliquée à une tâche démarrée.
Dans l’exemple de petit déjeuner, supposons que le grille-pain attrape le feu tout en grillant le pain. Vous pouvez simuler ce problème en modifiant la ToastBreadAsync
méthode pour qu’elle corresponde au code suivant :
private static async Task<Toast> ToastBreadAsync(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(2000);
Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");
await Task.Delay(1000);
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
Remarque
Lorsque vous compilez ce code, vous voyez un avertissement sur le code inaccessible. Cette erreur est liée à la conception. Après que le grille-pain prend feu, les opérations ne se poursuivent pas normalement et le code retourne une erreur.
Après avoir apporté les modifications du code, exécutez l’application et vérifiez la sortie :
Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 slices of bacon in the pan
Cooking first side of bacon...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a slice of bacon
Flipping a slice of bacon
Flipping a slice of bacon
Cooking the second side of bacon...
Cracking 2 eggs
Cooking the eggs ...
Put bacon on plate
Put eggs on plate
Eggs are ready
Bacon is ready
Unhandled exception. System.InvalidOperationException: The toaster is on fire
at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.cs:line 65
at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.cs:line 36
at AsyncBreakfast.Program.Main(String[] args) in Program.cs:line 24
at AsyncBreakfast.Program.<Main>(String[] args)
Notez que pas mal de tâches se terminent entre le moment où le grille-pain prend feu et celui où le système observe l’exception. Lorsqu’une tâche qui s’exécute de façon asynchrone lève une exception, cette tâche est défectueuse. L’objet Task
contient l’exception générée par la propriété Task.Exception. Les tâches défectueuses lèvent une exception lorsque l’expression await
est appliquée à la tâche.
Il existe deux mécanismes importants pour comprendre ce processus :
- Comment une exception est stockée dans une tâche défectueuse
- Comment une exception est déballée et relancée lorsque le code attend (
await
) sur une tâche défaillante
Lorsque le code en cours d’exécution lève de façon asynchrone une exception, l’exception est stockée dans l’objet Task
. La Task.Exception propriété est un System.AggregateException objet, car plusieurs exceptions peuvent être levées lors d'un travail asynchrone. Toute exception levée est ajoutée à la AggregateException.InnerExceptions collection. Si la propriété Exception
est nulle, un AggregateException
est créé et l'exception générée est le premier élément de la collection.
Le scénario le plus courant pour une tâche défectueuse est que la Exception
propriété contient exactement une exception. Lorsque votre code attend une tâche défaillante, il relance la première AggregateException.InnerExceptions exception de la collection. Ce résultat est la raison pour laquelle la sortie de l’exemple montre un System.InvalidOperationException objet plutôt qu’un AggregateException
objet. L’extraction de la première exception interne permet d’utiliser des méthodes asynchrones aussi similaires que possible pour utiliser leurs équivalents synchrones. Vous pouvez examiner la Exception
propriété dans votre code lorsque votre scénario peut générer plusieurs exceptions.
Conseil / Astuce
La pratique recommandée consiste à ce que toutes les exceptions de validation des arguments soient signalées de manière synchrone par les méthodes renvoyant des tâches. Pour plus d’informations et d’exemples, consultez Exceptions dans les méthodes de retour de tâches.
Avant de passer à la section suivante, commentez les deux instructions suivantes dans votre méthode ToastBreadAsync
. Vous ne voulez pas déclencher un autre feu :
Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");
Appliquer efficacement des expressions Await aux tâches
Vous pouvez améliorer la série d’expressions await
à la fin du code précédent à l’aide de méthodes de la Task
classe. Une API est la WhenAll méthode, qui retourne un Task objet qui se termine lorsque toutes les tâches de sa liste d’arguments sont terminées. Le code suivant illustre cette méthode :
await Task.WhenAll(eggsTask, baconTask, toastTask);
Console.WriteLine("Eggs are ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");
Une autre option consiste à utiliser la WhenAny méthode, qui retourne un Task<Task>
objet qui se termine lorsque l’un de ses arguments est terminé. Vous pouvez attendre la tâche retournée, car vous savez que la tâche est terminée. Le code suivant montre comment utiliser la WhenAny méthode pour attendre la fin de la première tâche, puis traiter son résultat. Une fois que vous avez traité le résultat de la tâche terminée, vous supprimez la tâche terminée de la liste des tâches passées à la WhenAny
méthode.
var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("Eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("Bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("Toast is ready");
}
await finishedTask;
breakfastTasks.Remove(finishedTask);
}
Près de la fin de l’extrait de code, notez l’expression await finishedTask;
. L’expression await Task.WhenAny
n’attend pas la tâche terminée, mais attend plutôt l’objet Task
retourné par la Task.WhenAny
méthode. Le résultat de la Task.WhenAny
méthode est la tâche terminée (ou défectueuse). La meilleure pratique consiste à attendre à nouveau la tâche, même lorsque vous savez que la tâche est terminée. De cette façon, vous pouvez récupérer le résultat de la tâche ou vérifier que toute exception qui provoque l’erreur de la tâche est levée.
Passer en revue le code final
Voici à quoi ressemble la version finale du code :
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AsyncBreakfast
{
// These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
internal class Bacon { }
internal class Coffee { }
internal class Egg { }
internal class Juice { }
internal class Toast { }
class Program
{
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
var eggsTask = FryEggsAsync(2);
var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);
var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("toast is ready");
}
await finishedTask;
breakfastTasks.Remove(finishedTask);
}
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);
return toast;
}
private static Juice PourOJ()
{
Console.WriteLine("Pouring orange juice");
return new Juice();
}
private static void ApplyJam(Toast toast) =>
Console.WriteLine("Putting jam on the toast");
private static void ApplyButter(Toast toast) =>
Console.WriteLine("Putting butter on the toast");
private static async Task<Toast> ToastBreadAsync(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(3000);
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
private static async Task<Bacon> FryBaconAsync(int slices)
{
Console.WriteLine($"putting {slices} slices of bacon in the pan");
Console.WriteLine("cooking first side of bacon...");
await Task.Delay(3000);
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
await Task.Delay(3000);
Console.WriteLine("Put bacon on plate");
return new Bacon();
}
private static async Task<Egg> FryEggsAsync(int howMany)
{
Console.WriteLine("Warming the egg pan...");
await Task.Delay(3000);
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
await Task.Delay(3000);
Console.WriteLine("Put eggs on plate");
return new Egg();
}
private static Coffee PourCoffee()
{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}
Le code effectue les tâches de petit déjeuner asynchrones en environ 15 minutes. Le temps total est réduit, car certaines tâches s’exécutent simultanément. Le code surveille simultanément plusieurs tâches et prend des mesures uniquement si nécessaire.
Le code final est asynchrone. Il reflète plus précisément la façon dont une personne peut cuisiner le petit déjeuner. Comparez le code final avec le premier exemple de code de l’article. Les actions principales sont toujours claires en lisant le code. Vous pouvez lire le code final de la même façon que vous lisez la liste des instructions pour faire un petit déjeuner, comme indiqué au début de l’article. Les fonctionnalités linguistiques des mots-clés async
et await
aident chaque personne à traduire les instructions écrites : Commencez les tâches dès que possible et ne restez pas bloqué en attendant qu'elles se terminent.