Przetwarzanie zadań asynchronicznych w miarę ich ukończenia (C#)
Za pomocą programu Task.WhenAnymożna jednocześnie uruchomić wiele zadań i przetworzyć je pojedynczo, ponieważ są one ukończone, a nie przetwarzać ich w kolejności, w której są uruchamiane.
W poniższym przykładzie użyto zapytania do utworzenia kolekcji zadań. Każde zadanie pobiera zawartość określonej witryny internetowej. W każdej iteracji pętli while oczekiwane wywołanie WhenAny zwraca zadanie w kolekcji zadań, które najpierw kończy pobieranie. To zadanie jest usuwane z kolekcji i przetwarzane. Pętla powtarza się, dopóki kolekcja nie zawiera więcej zadań.
Wymagania wstępne
Możesz wykonać czynności opisane w tym samouczku, korzystając z jednej z następujących opcji:
- Program Visual Studio 2022 z zainstalowanym obciążeniem tworzenia aplikacji klasycznych platformy .NET . Zestaw SDK platformy .NET jest instalowany automatycznie po wybraniu tego obciążenia.
- Zestaw SDK platformy .NET z wybranym edytorem kodu, takim jak Visual Studio Code.
Tworzenie przykładowej aplikacji
Utwórz nową aplikację konsolową platformy .NET Core. Można je utworzyć za pomocą polecenia dotnet new console lub programu Visual Studio.
Otwórz plik Program.cs w edytorze kodu i zastąp istniejący kod tym kodem:
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Dodawanie pól
Program
W definicji klasy dodaj następujące dwa pola:
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"
};
Funkcja HttpClient
uwidacznia możliwość wysyłania żądań HTTP i odbierania odpowiedzi HTTP. Zawiera s_urlList
wszystkie adresy URL, które aplikacja planuje przetworzyć.
Aktualizowanie punktu wejścia aplikacji
Głównym punktem wejścia do aplikacji konsolowej Main
jest metoda. Zastąp istniejącą metodę następującymi elementami:
static Task Main() => SumPageSizesAsync();
Zaktualizowana Main
metoda jest teraz uznawana za główny asynchroniczny, który umożliwia asynchroniczny punkt wejścia do pliku wykonywalnego. Jest wyrażona jako wywołanie metody SumPageSizesAsync
.
Tworzenie asynchronicznej metody sumowania rozmiarów stron
Main
Poniżej metody dodaj metodę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");
}
Pętla while
usuwa jedno z zadań w każdej iteracji. Po zakończeniu każdego zadania pętla kończy się. Metoda rozpoczyna się od utworzenia wystąpienia i uruchomienia Stopwatchelementu . Następnie zawiera zapytanie, które po wykonaniu tworzy kolekcję zadań. Każde wywołanie metody ProcessUrlAsync
w poniższym kodzie zwraca wartość Task<TResult>, gdzie TResult
jest liczbą całkowitą:
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
Ze względu na odroczone wykonanie za pomocą LINQ wywołaj polecenie Enumerable.ToList , aby uruchomić każde zadanie.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
Pętla while
wykonuje następujące kroki dla każdego zadania w kolekcji:
Oczekuje na wywołanie, aby
WhenAny
zidentyfikować pierwsze zadanie w kolekcji, które zakończyło pobieranie.Task<int> finishedTask = await Task.WhenAny(downloadTasks);
Usuwa to zadanie z kolekcji.
downloadTasks.Remove(finishedTask);
Oczekuje na
finishedTask
element , który jest zwracany przez wywołanie metodyProcessUrlAsync
. ZmiennafinishedTask
to Task<TResult> gdzieTResult
jest liczbą całkowitą. Zadanie zostało już ukończone, ale oczekujesz na pobranie długości pobranej witryny internetowej, jak pokazano w poniższym przykładzie. Jeśli zadanie zostanie uszkodzone,await
zgłosi pierwszy wyjątek podrzędny przechowywany wAggregateException
obiekcie , w przeciwieństwie do odczytu Task<TResult>.Result właściwości , co spowoduje zgłoszenieAggregateException
.total += await finishedTask;
Dodawanie metody procesu
Dodaj następującą ProcessUrlAsync
metodę SumPageSizesAsync
poniżej metody:
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;
}
Dla dowolnego adresu URL metoda użyje podanego client
wystąpienia, aby uzyskać odpowiedź jako byte[]
. Długość jest zwracana po zapisaniu adresu URL i długości do konsoli.
Uruchom program kilka razy, aby sprawdzić, czy pobrane długości nie zawsze są wyświetlane w tej samej kolejności.
Przestroga
Możesz użyć WhenAny
w pętli, zgodnie z opisem w przykładzie, aby rozwiązać problemy, które obejmują niewielką liczbę zadań. Jednak inne podejścia są bardziej wydajne, jeśli masz dużą liczbę zadań do przetworzenia. Aby uzyskać więcej informacji i przykładów, zobacz Przetwarzanie zadań podczas ich wykonywania.
Kompletny przykład
Poniższy kod jest kompletnym tekstem pliku Program.cs w tym przykładzie.
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