Traiter les tâches asynchrones terminées (C#)
En utilisant Task.WhenAny, vous pouvez démarrer plusieurs tâches en même temps et les traiter une par une, une fois qu’elles sont terminées, au lieu de les traiter dans l’ordre dans lequel elles ont démarré.
L’exemple suivant utilise une requête pour créer une collection de tâches. Chaque tâche télécharge le contenu d’un site web spécifié. À chaque itération d’une boucle while, un appel attendu à WhenAny retourne la tâche de la collection de tâches dont le téléchargement se termine en premier. Cette tâche est supprimée de la collection et traitée. La boucle se répète jusqu’à ce que la collection ne contienne plus aucune tâche.
Prérequis
Vous pouvez suivre ce didacticiel en utilisant l’une des options suivantes :
- Visual Studio 2022 avec la charge de travail Développement .NET Desktop installée. Le kit SDK .NET est automatiquement installé lorsque vous sélectionnez cette charge de travail.
- Le kit de développement logiciel (SDK) .NET avec un éditeur de code de votre choix, tel que Visual Studio Code.
Créer un exemple d’application
Créez une application console .NET Core. Vous pouvez en créer une à l’aide de la commande dotnet new console ou à partir de Visual Studio.
Ouvrez le fichier Program.cs dans votre éditeur de code, puis remplacez le code existant par ce code :
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Ajouter des champs
Dans la définition de la classe Program
, ajoutez les deux champs suivants :
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};
static readonly IEnumerable<string> s_urlList = new string[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
Le HttpClient
expose la possibilité d’envoyer des requêtes HTTP et de recevoir des réponses HTTP. Le s_urlList
contient toutes les URL que l’application prévoit de traiter.
Mettre à jour le point d’entrée de l’application
Le point d’entrée principal dans l’application console est la méthode Main
. Remplacez la méthode existante par ce qui suit :
static Task Main() => SumPageSizesAsync();
La méthode Main
mise à jour est désormais considérée comme une méthode Async main, ce qui autorise un point d’entrée asynchrone dans l’exécutable. Il est exprimé sous la forme d’un appel à SumPageSizesAsync
.
Créer la méthode de taille de page de somme asynchrone
Sous la méthode Main
, ajoutez la méthode SumPageSizesAsync
:
static async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
La boucle while
supprime l’une des tâches de chaque itération. Une fois chaque tâche terminée, la boucle se termine. La méthode commence par instancier et démarrer un Stopwatch. Elle comprend ensuite une requête qui, lorsqu’elle est exécutée, crée une collection de tâches. Chaque appel à ProcessUrlAsync
dans le code suivant retourne un Task<TResult>, où TResult
est un entier :
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
En raison d’une exécution différée avec LINQ, vous appelez Enumerable.ToList pour démarrer chaque tâche.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
La boucle while
effectue les étapes suivantes pour chaque tâche dans la collection :
Elle attend un appel à
WhenAny
pour identifier la première tâche dans la collection qui a terminé son téléchargement.Task<int> finishedTask = await Task.WhenAny(downloadTasks);
Elle supprime cette tâche de la collection.
downloadTasks.Remove(finishedTask);
Elle attend
finishedTask
, qui est retourné par un appel àProcessUrlAsync
. La variablefinishedTask
est un Task<TResult> oùTResult
est un entier. La tâche est déjà terminée, mais vous l’attendez pour récupérer la longueur du site web téléchargé, comme le montre l’exemple suivant. Si la tâche est défaillante,await
lève la première exception enfant stockée dansAggregateException
, contrairement à la lecture de la propriété Task<TResult>.Result, qui lève leAggregateException
.total += await finishedTask;
Ajouter une méthode de processus
Ajoutez la méthode suivante ProcessUrlAsync
sous la méthode SumPageSizesAsync
:
static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
Pour toute URL donnée, la méthode utilise l’instance client
fournie pour obtenir la réponse en tant que byte[]
. La longueur est retournée après l’URL et la longueur est écrite vers la console.
Exécutez le programme plusieurs fois pour vérifier que les longueurs téléchargées n’apparaissent pas toujours dans le même ordre.
Attention
Vous pouvez utiliser WhenAny
dans une boucle, comme décrit dans l’exemple, pour résoudre les problèmes qui impliquent un petit nombre de tâches. Cependant, d’autres approches sont plus efficaces si vous avez un grand nombre de tâches à traiter. Pour plus d’informations et d’exemples, consultez Traitement des tâches une fois terminées.
Exemple complet
Le code suivant est le texte complet du fichier Program.cs de l’exemple.
using System.Diagnostics;
HttpClient s_client = new()
{
MaxResponseContentBufferSize = 1_000_000
};
IEnumerable<string> s_urlList = new string[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
await SumPageSizesAsync();
async Task SumPageSizesAsync()
{
var stopwatch = Stopwatch.StartNew();
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
int total = 0;
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
stopwatch.Stop();
Console.WriteLine($"\nTotal bytes returned: {total:#,#}");
Console.WriteLine($"Elapsed time: {stopwatch.Elapsed}\n");
}
static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
byte[] content = await client.GetByteArrayAsync(url);
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
}
// Example output:
// https://learn.microsoft.com 132,517
// https://learn.microsoft.com/powershell 57,375
// https://learn.microsoft.com/gaming 33,549
// https://learn.microsoft.com/aspnet/core 88,714
// https://learn.microsoft.com/surface 39,840
// https://learn.microsoft.com/enterprise-mobility-security 30,903
// https://learn.microsoft.com/microsoft-365 67,867
// https://learn.microsoft.com/windows 26,816
// https://learn.microsoft.com/maui 57,958
// https://learn.microsoft.com/dotnet 78,706
// https://learn.microsoft.com/graph 48,277
// https://learn.microsoft.com/dynamics365 49,042
// https://learn.microsoft.com/office 67,867
// https://learn.microsoft.com/system-center 42,887
// https://learn.microsoft.com/education 38,636
// https://learn.microsoft.com/azure 421,663
// https://learn.microsoft.com/visualstudio 30,925
// https://learn.microsoft.com/sql 54,608
// https://learn.microsoft.com/azure/devops 86,034
// Total bytes returned: 1,454,184
// Elapsed time: 00:00:01.1290403