Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier les répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer de répertoire.
En utilisant Task.WhenAny, vous pouvez démarrer plusieurs tâches en même temps et les traiter une par une, au lieu de les traiter dans l’ordre dans lequel elles sont démarrées.
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é. Dans chaque itération d’une boucle while, un appel attendu pour WhenAny renvoyer la tâche dans la collection de tâches qui termine son téléchargement 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 de tâches.
Prerequisites
Vous pouvez suivre ce tutoriel à l’aide de l’une des options suivantes :
- Visual Studio 2022 avec la charge de travail de développement de bureau .NET installée. Le Kit de développement logiciel (SDK) .NET est installé automatiquement lorsque vous sélectionnez cette charge de travail.
- 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 un à l’aide de la commande dotnet new console ou à partir de Visual Studio.
Ouvrez le fichier Program.cs dans votre éditeur de code et 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 Program classe, 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"
};
Il HttpClient expose la possibilité d’envoyer des requêtes HTTP et de recevoir des réponses HTTP. Contient s_urlList 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 Main méthode. Remplacez la méthode existante par ce qui suit :
static Task Main() => SumPageSizesAsync();
La méthode mise à jour Main est désormais considérée comme un principal asynchrone, ce qui permet un point d’entrée asynchrone dans l’exécutable. Elle est exprimée sous la forme d’un appel à SumPageSizesAsync.
Créer la méthode de taille de page de somme asynchrone
Sous la Main méthode, ajoutez la SumPageSizesAsync méthode :
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 while boucle 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. Il inclut ensuite une requête qui, lorsqu’elle est exécutée, crée une collection de tâches. Chaque appel au ProcessUrlAsync code suivant retourne un Task<TResult>entier :TResult
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 while boucle effectue les étapes suivantes pour chaque tâche de la collection :
Attend un appel pour
WhenAnyidentifier la première tâche de la collection qui a terminé son téléchargement.Task<int> finishedTask = await Task.WhenAny(downloadTasks);Supprime cette tâche de la collection.
downloadTasks.Remove(finishedTask);Awaits
finishedTask, qui est retourné par un appel àProcessUrlAsync. LafinishedTaskvariable est un Task<TResult>TResultentier. 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 l’illustre l’exemple suivant. Si la tâche est défaillante,awaitlève la première exception enfant stockée dans leAggregateException, contrairement à la lecture de la Task<TResult>.Result propriété, qui lève leAggregateException.total += await finishedTask;
Ajouter une méthode de processus
Ajoutez la méthode suivante ProcessUrlAsync sous la SumPageSizesAsync méthode :
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 dans 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.
Caution
Vous pouvez utiliser WhenAny dans une boucle, comme décrit dans l’exemple, pour résoudre les problèmes impliquant un petit nombre de tâches. Toutefois, d’autres approches sont plus efficaces si vous avez un grand nombre de tâches à traiter. Pour plus d’informations et d’exemples, consultez Les tâches de traitement à mesure qu’elles se terminent.
Simplifier l’approche à l’aide de Task.WhenEach
La while boucle implémentée dans SumPageSizesAsync la méthode peut être simplifiée à l’aide de la nouvelle Task.WhenEach méthode introduite dans .NET 9, en l’appelant en await foreach boucle.
Remplacez la boucle précédemment implémentée while :
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
avec les opérations simplifiées await foreach:
await foreach (Task<int> t in Task.WhenEach(downloadTasks))
{
total += await t;
}
Cette nouvelle approche permet de ne plus appeler Task.WhenAny manuellement une tâche et de supprimer celle qui se termine, car Task.WhenEach effectue une itération dans une tâche dans un ordre de leur achèvement.
Exemple complet
Le code suivant est le texte complet du fichier Program.cs pour 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