Freigeben über


Abbrechen einer Aufgabenliste

Sie können eine asynchrone Konsolenanwendung abbrechen, wenn Sie nicht auf den Abschluss ihrer Ausführung warten möchten. Anhand des in diesem Artikel veranschaulichten Beispiels können Sie einen Abbruch zu einer Anwendung hinzufügen, die die Inhalte von einer Liste von Websites herunterlädt. Sie können viele Aufgaben abbrechen, indem Sie die CancellationTokenSource-Instanz jeder Aufgabe zuordnen. Wenn Sie die EINGABETASTE drücken, brechen Sie alle Aufgaben ab, die noch nicht abgeschlossen wurden.

In diesem Lernprogramm wird Folgendes behandelt:

  • Erstellen einer .NET-Konsolenanwendung
  • Schreiben einer asynchronen Anwendung, die einen Abbruch unterstützt
  • Veranschaulichung der Signalisierung eines Abbruchs

Voraussetzungen

Für dieses Lernprogramm ist Folgendes erforderlich:

Erstellen einer Beispielanwendung

Erstellen Sie eine neue .NET Core-Konsolenanwendung. Sie können eine solche Anwendung mithilfe des Befehls dotnet new console oder über Visual Studio erstellen. Öffnen Sie die Program.cs-Datei in Ihrem bevorzugten Code-Editor.

Ersetzen von using-Anweisungen

Ersetzen Sie die vorhandenen using-Anweisungen durch diese Deklarationen:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

Hinzufügen von Feldern

Fügen Sie die folgenden drei Felder in der Program-Klassendefinition hinzu:

static readonly CancellationTokenSource s_cts = new CancellationTokenSource();

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"
};

CancellationTokenSource wird dazu verwendet, einen angeforderten Abbruch für CancellationToken zu signalisieren. HttpClient ermöglicht das Senden und Empfangen von HTTP-Antworten. 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 async Task Main()
{
    Console.WriteLine("Application started.");
    Console.WriteLine("Press the ENTER key to cancel...\n");

    Task cancelTask = Task.Run(() =>
    {
        while (Console.ReadKey().Key != ConsoleKey.Enter)
        {
            Console.WriteLine("Press the ENTER key to cancel...");
        }

        Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
        s_cts.Cancel();
    });
    
    Task sumPageSizesTask = SumPageSizesAsync();

    Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
    if (finishedTask == cancelTask)
    {
        // wait for the cancellation to take place:
        try
        {
            await sumPageSizesTask;
            Console.WriteLine("Download task completed before cancel request was processed.");
        }
        catch (TaskCanceledException)
        {
            Console.WriteLine("Download task has been cancelled.");
        }
    }
        
    Console.WriteLine("Application ending.");
}

Die aktualisierte Main-Methode wird jetzt als Async main-Methode betrachtet, die einen asynchronen Einstiegspunkt in die ausführbare Datei ermöglicht. Sie schreibt einige Anweisungsnachrichten an die Konsole und deklariert dann eine Task-Instanz namens cancelTask, die Tastatureingaben in die Konsole liest. Wenn die EINGABETASTE gedrückt wird, wird ein Aufruf an CancellationTokenSource.Cancel() ausgeführt. Dies signalisiert den Abbruch. Dann wird eine sumPageSizesTask-Variable der SumPageSizesAsync-Methode zugewiesen. Beide Aufgaben werden dann an Task.WhenAny(Task[]) übergeben, was fortgesetzt wird, wenn eine der beiden Aufgaben abgeschlossen wird.

Der nächste Codeblock stellt sicher, dass die Anwendung erst beendet wird, wenn der Abbruch verarbeitet wurde. Wenn die erste auszuführende Aufgabe cancelTask ist, wird auf sumPageSizeTask gewartet. Wenn ein Abbruch durchgeführt und gewartet wird, wird System.Threading.Tasks.TaskCanceledException ausgelöst. Der Block fängt diese Ausnahme ab und gibt eine Nachricht aus.

Erstellen der SumPageSizesAsync-Methode

Fügen Sie unter der Main-Methode die SumPageSizesAsync-Methode hinzu:

static async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    int total = 0;
    foreach (string url in s_urlList)
    {
        int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
        total += contentLength;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:  {total:#,#}");
    Console.WriteLine($"Elapsed time:          {stopwatch.Elapsed}\n");
}

Die Methode beginnt mit dem Instanziieren und Starten einer Stopwatch-Klasse. Anschließend durchläuft sie alle URLs in s_urlList und ruft ProcessUrlAsync auf. Mit jeder Iteration wird s_cts.Token an die ProcessUrlAsync-Methode übergeben, und der Code gibt Task<TResult> zurück, wobei TResult ein Integer ist:

int total = 0;
foreach (string url in s_urlList)
{
    int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
    total += contentLength;
}

Hinzufügen der Prozessmethode

Fügen Sie die folgende ProcessUrlAsync-Methode unterhalb der SumPageSizesAsync-Methode hinzu:

static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
{
    HttpResponseMessage response = await client.GetAsync(url, token);
    byte[] content = await response.Content.ReadAsByteArrayAsync(token);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

Für eine beliebige URL verwendet die Methode die client-Instanz, die bereitgestellt wird, um die Antwort als byte[] zu erhalten. Die CancellationToken-Instanz wird an die Methoden HttpClient.GetAsync(String, CancellationToken) und HttpContent.ReadAsByteArrayAsync() übergeben. token wird dazu verwendet, den angeforderten Abbruch zu registrieren. Die Länge wird zurückgegeben, nachdem die URL und die Länge in die Konsole geschrieben wurden.

Ausgabe der Beispielanwendung

Application started.
Press the ENTER key to cancel...

https://learn.microsoft.com                                       37,357
https://learn.microsoft.com/aspnet/core                           85,589
https://learn.microsoft.com/azure                                398,939
https://learn.microsoft.com/azure/devops                          73,663
https://learn.microsoft.com/dotnet                                67,452
https://learn.microsoft.com/dynamics365                           48,582
https://learn.microsoft.com/education                             22,924

ENTER key pressed: cancelling downloads.

Application ending.

Vollständiges Beispiel

Der folgende Code besteht aus dem vollständigen Text der Program.cs-Datei für das Beispiel.

using System.Diagnostics;

class Program
{
    static readonly CancellationTokenSource s_cts = new CancellationTokenSource();

    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"
    };

    static async Task Main()
    {
        Console.WriteLine("Application started.");
        Console.WriteLine("Press the ENTER key to cancel...\n");

        Task cancelTask = Task.Run(() =>
        {
            while (Console.ReadKey().Key != ConsoleKey.Enter)
            {
                Console.WriteLine("Press the ENTER key to cancel...");
            }

            Console.WriteLine("\nENTER key pressed: cancelling downloads.\n");
            s_cts.Cancel();
        });

        Task sumPageSizesTask = SumPageSizesAsync();

        Task finishedTask = await Task.WhenAny(new[] { cancelTask, sumPageSizesTask });
        if (finishedTask == cancelTask)
        {
            // wait for the cancellation to take place:
            try
            {
                await sumPageSizesTask;
                Console.WriteLine("Download task completed before cancel request was processed.");
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Download task has been cancelled.");
            }
        }

        Console.WriteLine("Application ending.");
    }

    static async Task SumPageSizesAsync()
    {
        var stopwatch = Stopwatch.StartNew();

        int total = 0;
        foreach (string url in s_urlList)
        {
            int contentLength = await ProcessUrlAsync(url, s_client, s_cts.Token);
            total += contentLength;
        }

        stopwatch.Stop();

        Console.WriteLine($"\nTotal bytes returned:  {total:#,#}");
        Console.WriteLine($"Elapsed time:          {stopwatch.Elapsed}\n");
    }

    static async Task<int> ProcessUrlAsync(string url, HttpClient client, CancellationToken token)
    {
        HttpResponseMessage response = await client.GetAsync(url, token);
        byte[] content = await response.Content.ReadAsByteArrayAsync(token);
        Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

        return content.Length;
    }
}

Weitere Informationen

Nächste Schritte