Zpracování asynchronních úloh při jejich dokončení (C#)
Pomocí příkazu Task.WhenAnymůžete spustit několik úkolů současně a zpracovávat je jeden po druhém, jakmile jsou dokončené, místo toho, abyste je zpracovávali v pořadí, ve kterém byly spuštěny.
Následující příklad používá dotaz k vytvoření kolekce úloh. Každý úkol stáhne obsah zadaného webu. V každé iteraci smyčky while očekávané volání WhenAny vrátí úlohu v kolekci úloh, která dokončí stahování jako první. Tato úloha se odebere z kolekce a zpracuje se. Smyčka se opakuje, dokud kolekce neobsahuje žádné další úlohy.
Požadavky
V tomto kurzu můžete využít jednu z následujících možností:
- Visual Studio 2022 s nainstalovanou úlohou Vývoj desktopových aplikací .NET Sada .NET SDK se automaticky nainstaluje, když vyberete tuto úlohu.
- Sada .NET SDK s editorem kódu podle vašeho výběru, jako je například Visual Studio Code.
Vytvoření ukázkové aplikace
Vytvořte novou konzolovou aplikaci .NET Core. Můžete ho vytvořit pomocí příkazu konzoly dotnet new nebo ze sady Visual Studio.
V editoru kódu otevřete soubor Program.cs a nahraďte stávající kód tímto kódem:
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Přidání polí
Program
Do definice třídy přidejte následující dvě pole:
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"
};
Zpřístupňuje HttpClient
možnost odesílat požadavky HTTP a přijímat odpovědi HTTP. Obsahuje s_urlList
všechny adresy URL, které aplikace plánuje zpracovat.
Aktualizace vstupního bodu aplikace
Hlavním vstupním bodem do konzolové aplikace je Main
metoda . Nahraďte existující metodu následujícím kódem:
static Task Main() => SumPageSizesAsync();
Aktualizovaná Main
metoda je nyní považována za Async main, která umožňuje asynchronní vstupní bod do spustitelného souboru. Je vyjádřena voláním SumPageSizesAsync
metody .
Vytvoření metody asynchronního součtu velikostí stránek
Pod metodu Main
přidejte metodu 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");
}
Smyčka while
odebere jednu z úloh v každé iteraci. Po dokončení každého úkolu smyčka skončí. Metoda začíná vytvořením instance a spuštěním Stopwatch. Potom obsahuje dotaz, který při spuštění vytvoří kolekci úloh. Každé volání ProcessUrlAsync
v následujícím kódu vrátí , Task<TResult>kde TResult
je celé číslo:
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
Z důvodu odloženého spuštění s LINQ zavoláte Enumerable.ToList ke spuštění každého úkolu.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
Smyčka while
provede následující kroky pro každou úlohu v kolekci:
Čeká na
WhenAny
volání k identifikaci první úlohy v kolekci, která dokončila stahování.Task<int> finishedTask = await Task.WhenAny(downloadTasks);
Odebere tento úkol z kolekce.
downloadTasks.Remove(finishedTask);
Awaits
finishedTask
, která je vrácena voláním metodyProcessUrlAsync
. ProměnnáfinishedTask
je kde Task<TResult>TResult
je celé číslo. Úkol je již dokončen, ale čekáte na načtení délky staženého webu, jak ukazuje následující příklad. Pokud je úloha chybná,await
vyvolá první podřízenou výjimku uloženou v objektuAggregateException
, na rozdíl od čtení Task<TResult>.Result vlastnosti, která by vyvolalaAggregateException
výjimku .total += await finishedTask;
Přidání metody procesu
Pod metodu přidejte následující ProcessUrlAsync
metodu 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;
}
Pro každou danou adresu URL metoda použije zadanou client
byte[]
instanci k získání odpovědi jako . Délka je vrácena po adrese URL a délka je zapsána do konzoly.
Spusťte program několikrát, abyste ověřili, že se stažené délky nezobrazují vždy ve stejném pořadí.
Upozornění
Pomocí smyčky in, jak je popsáno v příkladu, můžete WhenAny
vyřešit problémy, které zahrnují malý počet úloh. Jiné přístupy jsou ale efektivnější, pokud potřebujete zpracovat velký počet úkolů. Další informace a příklady najdete v tématu Zpracování úloh při jejich dokončení.
Kompletní příklad
Následující kód je úplný text souboru Program.cs pro příklad.
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