Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Za pomocą programu Task.WhenAnymożna jednocześnie uruchomić wiele zadań i przetworzyć je jeden po drugim, zamiast przetwarzać je 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ńczą pobieranie. To zadanie jest usuwane z kolekcji i przetwarzane. Pętla powtarza się, dopóki kolekcja nie zawiera więcej zadań.
Wymagania wstępne
Aby wykonać czynności opisane w tym samouczku, użyj jednej z następujących opcji:
- programu Visual Studio 2022 z zainstalowanym pakietem do tworzenia aplikacji klasycznych .NET . Zestaw .NET SDK jest instalowany automatycznie po wybraniu tego obciążenia.
- Zestaw .NET SDK z wybranym edytorem kodu, takim jak Visual Studio Code.
Tworzenie przykładowej aplikacji
Utwórz nową aplikację konsolową platformy .NET Core. Można go utworzyć przy użyciu polecenia dotnet new console lub programu Visual Studio.
Otwórz plik Program.cs w edytorze kodu i zastąp istniejący kod następującym 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 jest Main metoda . Zastąp istniejącą metodę następującym kodem:
static Task Main() => SumPageSizesAsync();
Zaktualizowana Main metoda jest teraz traktowana jako główna asynchroniczna, która umożliwia asynchroniczny punkt wejścia do pliku wykonywalnego. Jest wyrażona jako wywołanie metody SumPageSizesAsync.
Tworzenie metody asynchronicznej 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 kończy się pętla. Metoda rozpoczyna się od utworzenia wystąpienia i uruchomienia klasy Stopwatch. Następnie zawiera zapytanie, które po wykonaniu tworzy kolekcję zadań. Każde wywołanie metody ProcessUrlAsync w poniższym kodzie zwraca Task<TResult>wartość , 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łujesz 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
WhenAnyzidentyfikować 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);Awaits
finishedTask, który jest zwracany przez wywołanie metodyProcessUrlAsync. ZmiennafinishedTaskTask<TResult> to gdzieTResultjest 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,awaitzgłosi pierwszy wyjątek podrzędny przechowywany wAggregateExceptionobiekcie , w przeciwieństwie do odczytywania Task<TResult>.Result właściwości , która zgłaszaAggregateExceptionbłąd .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;
}
W przypadku 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.
Ostrzeżenie
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.
Uproszczenie podejścia przy użyciu Task.WhenEach
Pętla while zaimplementowana w SumPageSizesAsync metodzie można uprościć przy użyciu nowej Task.WhenEach metody wprowadzonej na platformie .NET 9, wywołując ją w await foreach pętli.
Zastąp wcześniej zaimplementowaną while pętlę:
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
z uproszczonym await foreach:
await foreach (Task<int> t in Task.WhenEach(downloadTasks))
{
total += await t;
}
To nowe podejście umożliwia nie wielokrotne wywoływanie Task.WhenAny ręcznego wywołania zadania i usunięcie tego, który zostanie zakończony, ponieważ Task.WhenEach iteruje zadanie w kolejności ich ukończenia.
Kompletny przykład
Poniższy kod jest kompletnym tekstem pliku Program.cs dla przykładu.
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