Annullare un elenco di attività

Se non si vuole attendere il completamento, è possibile annullare un'applicazione console asincrona. Seguendo gli esempi in questo argomento, è possibile aggiungere l’annullamento di un'applicazione che scarica il contenuto di un elenco di siti Web. È possibile annullare molte attività associando l'istanza di CancellationTokenSource a ogni attività. Se si seleziona la chiave Invio, annullare tutte le attività non ancora completate.

Contenuto dell'esercitazione:

  • Creazione di un'applicazione console .NET
  • Scrittura di un'applicazione asincrona che supporta l'annullamento
  • Dimostrazione dell'annullamento della segnalazione

Prerequisiti

Per completare questa esercitazione, è necessario disporre di:

Creare un'applicazione di esempio

Creare una nuova applicazione console .NET Core. È possibile crearne una usando il comando dotnet new console o da Visual Studio. Aprire il file Program.cs nell'editor di codice preferito.

Sostituire le istruzioni

Sostituire le istruzioni esistenti con queste dichiarazioni:

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

Aggiungi campi

Nella definizione della classe Program aggiungere questi tre campi:

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 viene utilizzato per segnalare un annullamento richiesto a un oggetto CancellationToken. HttpClient espone la possibilità di inviare richieste HTTP e ricevere risposte HTTP. s_urlList contiene tutti gli URL che l'applicazione prevede di elaborare.

Aggiornare il punto di ingresso dell'applicazione

Il punto di ingresso principale nell'applicazione console è il metodo Main. Sostituire il metodo esistente con quanto segue:

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

Il metodo Main aggiornato viene ora considerato un Async Main, che consente un punto di ingresso asincrono nel file eseguibile. Scrive alcuni messaggi di istruzioni nella console, quindi dichiara un'istanza Task denominata cancelTask, che leggerà i tratti chiave della console. Se viene premuto il tasto Invio, viene effettuata una chiamata a CancellationTokenSource.Cancel(). In questo modo verrà segnalato l'annullamento. Successivamente, la variabile sumPageSizesTask viene assegnata dal SumPageSizesAsync metodo. Entrambe le attività vengono quindi passate a Task.WhenAny(Task[]), che continuerà quando una delle due attività è stata completata.

Il blocco di codice successivo garantisce che l'applicazione non venga chiusa fino a quando l'annullamento non è stato elaborato. Se la prima attività da completare è cancelTask, sumPageSizeTask resta in attesa. Se è stato annullato, quando è in attesa genera un'eccezione System.Threading.Tasks.TaskCanceledException. Il blocco intercetta tale eccezione e stampa un messaggio.

Creare il metodo SumPageSizes asincrono

Sotto il metodo Main aggiungere il metodo SumPageSizesAsync:

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

Il metodo inizia creando un'istanza e avviando una classe Stopwatch. Viene quindi eseguito il ciclo di ogni URL nell'oggetto s_urlList e chiama ProcessUrlAsync. Con ogni iterazione, il s_cts.Token viene passato al metodo ProcessUrlAsync e il codice restituisce un Task<TResult>, dove TResult è un numero intero:

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

Aggiungere un metodo di elaborazione

Aggiungere il metodo ProcessUrlAsync seguente sotto il metodo SumPageSizesAsync:

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

Per qualsiasi URL specificato, il metodo userà l'istanza di client fornita per ottenere la risposta come byte[]. L'istanza CancellationToken viene passata ai HttpClient.GetAsync(String, CancellationToken) metodi e HttpContent.ReadAsByteArrayAsync(). token viene utilizzato per la registrazione per l'annullamento richiesto. La lunghezza viene restituita dopo l'URL e la lunghezza viene scritta nella console.

Output dell'applicazione di esempio

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.

Esempio completo

Il codice seguente è il testo completo del file Program.cs per l'esempio.

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

Vedi anche

Passaggi successivi