Asynchrone taken verwerken tijdens het voltooien (C#)
Met behulp van Task.WhenAnykunt u meerdere taken tegelijk starten en ze één voor één verwerken wanneer ze worden voltooid in plaats van ze te verwerken in de volgorde waarin ze worden gestart.
In het volgende voorbeeld wordt een query gebruikt om een verzameling taken te maken. Elke taak downloadt de inhoud van een opgegeven website. In elke herhaling van een while-lus retourneert een wachtende aanroep om WhenAny de taak te retourneren in de verzameling taken die het downloaden als eerste is voltooid. Deze taak wordt verwijderd uit de verzameling en verwerkt. De lus wordt herhaald totdat de verzameling geen taken meer bevat.
Vereisten
U kunt deze zelfstudie volgen met behulp van een van de volgende opties:
- Visual Studio 2022 met de workload .NET Desktop Development geïnstalleerd. De .NET SDK wordt automatisch geïnstalleerd wanneer u deze workload selecteert.
- De .NET SDK met een code-editor van uw keuze, zoals Visual Studio Code.
Voorbeeldtoepassing maken
Maak een nieuwe .NET Core-consoletoepassing. U kunt er een maken met behulp van de opdracht dotnet new console of vanuit Visual Studio.
Open het bestand Program.cs in de code-editor en vervang de bestaande code door deze code:
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Velden toevoegen
Voeg in de Program
klassedefinitie de volgende twee velden toe:
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"
};
De HttpClient
biedt de mogelijkheid om HTTP-aanvragen te verzenden en HTTP-antwoorden te ontvangen. De s_urlList
bevat alle URL's die de toepassing wil verwerken.
Toepassingsinvoerpunt bijwerken
Het belangrijkste toegangspunt in de consoletoepassing is de Main
methode. Vervang de bestaande methode door het volgende:
static Task Main() => SumPageSizesAsync();
De bijgewerkte Main
methode wordt nu beschouwd als een Asynchrone hoofd, waarmee een asynchroon toegangspunt in het uitvoerbare bestand kan worden gemaakt. Het wordt uitgedrukt als een aanroep van SumPageSizesAsync
.
De asynchrone methode voor paginaformaten voor som maken
Voeg onder de Main
methode de methode toe 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");
}
De while
lus verwijdert een van de taken in elke iteratie. Nadat elke taak is voltooid, wordt de lus beëindigd. De methode begint met het instantiëren en starten van een Stopwatch. Het bevat vervolgens een query die, wanneer deze wordt uitgevoerd, een verzameling taken maakt. Elke aanroep naar ProcessUrlAsync
in de volgende code retourneert een Task<TResult>, waarbij TResult
een geheel getal is:
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
Vanwege de uitgestelde uitvoering met de LINQ roept u Enumerable.ToList aan om elke taak te starten.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
De while
lus voert de volgende stappen uit voor elke taak in de verzameling:
Wacht op een aanroep om
WhenAny
de eerste taak in de verzameling te identificeren die het downloaden heeft voltooid.Task<int> finishedTask = await Task.WhenAny(downloadTasks);
Hiermee verwijdert u die taak uit de verzameling.
downloadTasks.Remove(finishedTask);
finishedTask
Wacht op , dat wordt geretourneerd door een aanroep naarProcessUrlAsync
. DefinishedTask
variabele is een Task<TResult> waarbijTResult
een geheel getal is. De taak is al voltooid, maar u wacht erop om de lengte van de gedownloade website op te halen, zoals in het volgende voorbeeld wordt weergegeven. Als de taak een fout heeft,await
genereert de eerste onderliggende uitzondering die is opgeslagen in deAggregateException
, in tegenstelling tot het lezen van de Task<TResult>.Result eigenschap, waardoor deAggregateException
wordt gegenereerd.total += await finishedTask;
Procesmethode toevoegen
Voeg de volgende ProcessUrlAsync
methode toe onder de SumPageSizesAsync
methode:
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;
}
Voor een bepaalde URL gebruikt de methode het client
opgegeven exemplaar om het antwoord op te halen als een byte[]
. De lengte wordt geretourneerd nadat de URL en de lengte naar de console is geschreven.
Voer het programma meerdere keren uit om te controleren of de gedownloade lengten niet altijd in dezelfde volgorde worden weergegeven.
Waarschuwing
U kunt in een lus gebruiken WhenAny
, zoals beschreven in het voorbeeld, om problemen op te lossen die betrekking hebben op een klein aantal taken. Andere benaderingen zijn echter efficiënter als u een groot aantal taken moet verwerken. Zie Taken verwerken wanneer ze zijn voltooid voor meer informatie en voorbeelden.
Volledig voorbeeld
De volgende code is de volledige tekst van het bestand Program.cs voor het voorbeeld.
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