Annuler une liste de tâches

Vous pouvez annuler une application console asynchrone si vous ne souhaitez pas attendre qu’elle se termine. Les exemples de cette rubrique vous montrent comment ajouter une annulation à une application qui télécharge du contenu d’une liste de sites web. Vous pouvez annuler de nombreuses tâches en associant l’instance CancellationTokenSource à chaque tâche. Si vous sélectionnez la touche Entrée, vous annulez toutes les tâches qui ne sont pas encore terminées.

Ce didacticiel contient les sections suivantes :

  • Création d’une application console .NET
  • Écriture d’une application asynchrone prenant en charge l’annulation
  • Démonstration de l’annulation de la signalisation

Prérequis

Ce didacticiel requiert les éléments suivants :

Créer un exemple d’application

Créez une application console .NET Core. Vous pouvez en créer une à l’aide de la commande dotnet new console ou à partir de Visual Studio. Ouvrez le fichier Program. cs dans votre éditeur de code favori.

Remplacer les instructions using

Remplacez les instructions using existantes par ces déclarations :

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

Ajouter des champs

Dans la définition de classe Program, ajoutez ces trois champs :

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

Le CancellationTokenSource est utilisé pour signaler une annulation demandée à un CancellationToken. Le HttpClient expose la possibilité d’envoyer des requêtes HTTP et de recevoir des réponses HTTP. Le s_urlList contient toutes les URL que l’application prévoit de traiter.

Mettre à jour le point d’entrée de l’application

Le point d’entrée principal dans l’application console est la méthode Main. Remplacez la méthode existante par ce qui suit :

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

La méthode Main mise à jour est désormais considérée comme une Async main, ce qui autorise un point d’entrée asynchrone dans l’exécutable. Il écrit quelques messages d’instruction dans la console, puis déclare une instance Task nommée cancelTask, qui lit les séquences de touches de la console. Si la touche Entrée est utilisée, un appel à CancellationTokenSource.Cancel() est effectué. Cela signale l’annulation. Ensuite, la variable sumPageSizesTask est affectée à partir de la méthode SumPageSizesAsync. Les deux tâches sont ensuite passées à Task.WhenAny(Task[]), qui se poursuit une fois l’une des deux tâches terminée.

Le bloc de code suivant garantit que l’application ne se ferme pas tant que l’annulation n’a pas été traitée. Si la première tâche à effectuer est cancelTask, la tâche sumPageSizeTask est attendue. Si elle a été annulée, lorsqu’elle est attendue, cela lève une System.Threading.Tasks.TaskCanceledException. Le bloc intercepte cette exception et imprime un message.

Créer la méthode de taille de page de somme asynchrone

Sous la méthode Main, ajoutez la méthode 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");
}

La méthode commence par instancier et démarrer un Stopwatch. Elle effectue ensuite une boucle à travers chaque URL dans les appels s_urlList et appelle ProcessUrlAsync. Avec chaque itération, le s_cts.Token est passé dans la méthode ProcessUrlAsync et le code retourne un Task<TResult>TResult est un entier :

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

Ajouter une méthode de processus

Ajoutez la méthode suivante ProcessUrlAsync sous la méthode 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;
}

Pour toute URL donnée, la méthode utilise l’instance client fournie pour obtenir la réponse en tant que byte[]. L’instance CancellationToken est passée dans les méthodes HttpClient.GetAsync(String, CancellationToken) et HttpContent.ReadAsByteArrayAsync(). token est utilisé pour s’inscrire à l’annulation demandée. La longueur est retournée après l’URL et la longueur est écrite vers la console.

Exemple de sortie d’application

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.

Exemple complet

Le code suivant est le texte complet du fichier Program.cs de l’exemple.

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

Voir aussi

Étapes suivantes