Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Mithilfe von Task.WhenAny" können Sie mehrere Aufgaben gleichzeitig starten und sie einzeln verarbeiten, da sie abgeschlossen sind, anstatt sie in der Reihenfolge zu verarbeiten, in der sie gestartet werden.
Im folgenden Beispiel wird eine Abfrage verwendet, um eine Sammlung von Aufgaben zu erstellen. Jede Aufgabe lädt den Inhalt einer angegebenen Website herunter. Bei jeder Iteration einer While-Schleife gibt ein abgewarteter Aufruf an WhenAny die Aufgabenstellung in der Aufgabensammlung zurück, die den Download zuerst abgeschlossen hat. Diese Aufgabe wird aus der Sammlung entfernt und verarbeitet. Die Schleife wird wiederholt, bis die Auflistung keine weiteren Aufgaben enthält.
Voraussetzungen
Sie können diesem Lernprogramm folgen, indem Sie eine der folgenden Optionen verwenden:
- Visual Studio 2022 mit installierter Workload .NET-Desktopentwicklung. Das .NET SDK wird automatisch installiert, wenn Sie diese Workload auswählen.
- Das .NET SDK mit einem Code-Editor Ihrer Wahl, z. B. Visual Studio Code.
Beispielanwendung erstellen
Erstellen Sie eine neue .NET Core-Konsolenanwendung. Sie können einen erstellen, indem Sie den dotnet new console Befehl oder Visual Studio verwenden.
Öffnen Sie die Program.cs Datei im Code-Editor, und ersetzen Sie den vorhandenen Code durch diesen Code:
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Hinzufügen von Feldern
Fügen Sie in der Program Klassendefinition die folgenden beiden Felder hinzu:
static readonly HttpClient s_client = new HttpClient
{
MaxResponseContentBufferSize = 1_000_000
};
static readonly IEnumerable<string> s_urlList = new string[]
{
"https://dori-uw-1.kuma-moon.com",
"https://dori-uw-1.kuma-moon.com/aspnet/core",
"https://dori-uw-1.kuma-moon.com/azure",
"https://dori-uw-1.kuma-moon.com/azure/devops",
"https://dori-uw-1.kuma-moon.com/dotnet",
"https://dori-uw-1.kuma-moon.com/dynamics365",
"https://dori-uw-1.kuma-moon.com/education",
"https://dori-uw-1.kuma-moon.com/enterprise-mobility-security",
"https://dori-uw-1.kuma-moon.com/gaming",
"https://dori-uw-1.kuma-moon.com/graph",
"https://dori-uw-1.kuma-moon.com/microsoft-365",
"https://dori-uw-1.kuma-moon.com/office",
"https://dori-uw-1.kuma-moon.com/powershell",
"https://dori-uw-1.kuma-moon.com/sql",
"https://dori-uw-1.kuma-moon.com/surface",
"https://dori-uw-1.kuma-moon.com/system-center",
"https://dori-uw-1.kuma-moon.com/visualstudio",
"https://dori-uw-1.kuma-moon.com/windows",
"https://dori-uw-1.kuma-moon.com/maui"
};
Dies HttpClient macht die Möglichkeit zum Senden von HTTP-Anforderungen und zum Empfangen von HTTP-Antworten verfügbar.
s_urlList enthält alle URLs, die von der Anwendung verarbeitet werden sollen.
Aktualisieren des Einstiegspunkts der Anwendung
Der Haupteinstiegspunkt in die Konsolenanwendung ist die Main Methode. Ersetzen Sie die vorhandene Methode durch Folgendes:
static Task Main() => SumPageSizesAsync();
Die aktualisierte Main Methode gilt nun als Async Main, das einen asynchronen Einstiegspunkt in der ausführbaren Datei ermöglicht. Sie wird als Aufruf zu SumPageSizesAsync ausgedrückt.
Asynchrone Methode zur Summierung von Seitengrößen erstellen
Fügen Sie unter der Main Methode die SumPageSizesAsync Methode hinzu:
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");
}
Die while Schleife entfernt eine der Aufgaben in jeder Iteration. Nach Abschluss jeder Aufgabe endet die Schleife. Die Methode beginnt mit dem Instanziieren und Starten eines Stopwatch. Anschließend wird eine Abfrage eingeschlossen, die beim Ausführen eine Sammlung von Aufgaben erstellt. Jeder Aufruf von ProcessUrlAsync im folgenden Code gibt ein Task<TResult> zurück, wobei TResult eine ganze Zahl ist.
IEnumerable<Task<int>> downloadTasksQuery =
from url in s_urlList
select ProcessUrlAsync(url, s_client);
Aufgrund der verzögerten Ausführung in LINQ müssen Sie Enumerable.ToList aufrufen, um jede Aufgabe zu starten.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
Die while Schleife führt die folgenden Schritte für jede Aufgabe in der Auflistung aus:
Wartet auf einen Aufruf an
WhenAny, um festzustellen, welche die erste Aufgabe in der Sammlung ist, die ihren Download abgeschlossen hat.Task<int> finishedTask = await Task.WhenAny(downloadTasks);Entfernt diese Aufgabe aus der Auflistung.
downloadTasks.Remove(finishedTask);Wartet auf
finishedTask, das durch einen Aufruf vonProcessUrlAsynczurückgegeben wird. DiefinishedTaskVariable ist ein Task<TResult>, wobeiTResulteine ganze Zahl ist. Die Aufgabe ist bereits abgeschlossen, aber Sie warten darauf, die Länge der heruntergeladenen Website abzurufen, wie im folgenden Beispiel gezeigt. Wenn die Aufgabe fehlerhaft ist, führtawaitdazu, dass die erste untergeordnete Ausnahme, die inAggregateExceptiongespeichert ist, ausgelöst wird, im Gegensatz zum Lesen der Task<TResult>.Result-Eigenschaft, die dieAggregateExceptionauslösen würde.total += await finishedTask;
Hinzufügen einer Prozessmethode
Fügen Sie die folgende ProcessUrlAsync Methode unter der SumPageSizesAsync Methode hinzu:
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;
}
Für jede angegebene URL nutzt die Methode die Instanz client, um die Antwort als byte[] zu erhalten. Die Länge wird zurückgegeben, nachdem die URL und die Länge in die Konsole geschrieben wurden.
Führen Sie das Programm mehrmals aus, um sicherzustellen, dass die heruntergeladenen Längen nicht immer in derselben Reihenfolge angezeigt werden.
Vorsicht
Sie können WhenAny in einer Schleife verwenden, wie im Beispiel beschrieben, um Probleme zu lösen, die eine kleine Anzahl von Aufgaben umfassen. Andere Ansätze sind jedoch effizienter, wenn Sie über eine große Anzahl von Aufgaben verfügen, die verarbeitet werden sollen. Weitere Informationen und Beispiele finden Sie unter "Verarbeitungsaufgaben, wenn sie abgeschlossen sind".
Vereinfachen des Ansatzes mithilfe von Task.WhenEach
Die in der Methode SumPageSizesAsync implementierte while Schleife kann mithilfe der in .NET 9 eingeführten neuen Methode Task.WhenEach vereinfacht werden, indem diese in der await foreach Schleife aufgerufen wird.
Ersetzen Sie die zuvor implementierte while Schleife:
while (downloadTasks.Any())
{
Task<int> finishedTask = await Task.WhenAny(downloadTasks);
downloadTasks.Remove(finishedTask);
total += await finishedTask;
}
mit dem vereinfachten await foreach:
await foreach (Task<int> t in Task.WhenEach(downloadTasks))
{
total += await t;
}
Dieser neue Ansatz ermöglicht es, Task.WhenAny nicht mehr wiederholt manuell aufzurufen und die Aufgaben zu entfernen, die abgeschlossen sind, da Task.WhenEach die Aufgaben in der Reihenfolge ihrer Fertigstellung durchläuft.
Vollständiges Beispiel
Der folgende Code ist der vollständige Text der Program.cs-Datei für das Beispiel.
using System.Diagnostics;
HttpClient s_client = new()
{
MaxResponseContentBufferSize = 1_000_000
};
IEnumerable<string> s_urlList = new string[]
{
"https://dori-uw-1.kuma-moon.com",
"https://dori-uw-1.kuma-moon.com/aspnet/core",
"https://dori-uw-1.kuma-moon.com/azure",
"https://dori-uw-1.kuma-moon.com/azure/devops",
"https://dori-uw-1.kuma-moon.com/dotnet",
"https://dori-uw-1.kuma-moon.com/dynamics365",
"https://dori-uw-1.kuma-moon.com/education",
"https://dori-uw-1.kuma-moon.com/enterprise-mobility-security",
"https://dori-uw-1.kuma-moon.com/gaming",
"https://dori-uw-1.kuma-moon.com/graph",
"https://dori-uw-1.kuma-moon.com/microsoft-365",
"https://dori-uw-1.kuma-moon.com/office",
"https://dori-uw-1.kuma-moon.com/powershell",
"https://dori-uw-1.kuma-moon.com/sql",
"https://dori-uw-1.kuma-moon.com/surface",
"https://dori-uw-1.kuma-moon.com/system-center",
"https://dori-uw-1.kuma-moon.com/visualstudio",
"https://dori-uw-1.kuma-moon.com/windows",
"https://dori-uw-1.kuma-moon.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://dori-uw-1.kuma-moon.com 132,517
// https://dori-uw-1.kuma-moon.com/powershell 57,375
// https://dori-uw-1.kuma-moon.com/gaming 33,549
// https://dori-uw-1.kuma-moon.com/aspnet/core 88,714
// https://dori-uw-1.kuma-moon.com/surface 39,840
// https://dori-uw-1.kuma-moon.com/enterprise-mobility-security 30,903
// https://dori-uw-1.kuma-moon.com/microsoft-365 67,867
// https://dori-uw-1.kuma-moon.com/windows 26,816
// https://dori-uw-1.kuma-moon.com/maui 57,958
// https://dori-uw-1.kuma-moon.com/dotnet 78,706
// https://dori-uw-1.kuma-moon.com/graph 48,277
// https://dori-uw-1.kuma-moon.com/dynamics365 49,042
// https://dori-uw-1.kuma-moon.com/office 67,867
// https://dori-uw-1.kuma-moon.com/system-center 42,887
// https://dori-uw-1.kuma-moon.com/education 38,636
// https://dori-uw-1.kuma-moon.com/azure 421,663
// https://dori-uw-1.kuma-moon.com/visualstudio 30,925
// https://dori-uw-1.kuma-moon.com/sql 54,608
// https://dori-uw-1.kuma-moon.com/azure/devops 86,034
// Total bytes returned: 1,454,184
// Elapsed time: 00:00:01.1290403