231 votes

Jeton d'annulation dans le constructeur de la tâche : pourquoi ?

Certain System.Threading.Tasks.Task prennent un CancellationToken comme paramètre :

CancellationTokenSource source = new CancellationTokenSource();
Task t = new Task (/* method */, source.Token);

Ce qui me laisse perplexe, c'est qu'il n'y a aucun moyen de passer de la à l'intérieur le corps de la méthode pour accéder au jeton transmis (par exemple, rien de tel que Task.CurrentTask.CancellationToken ). Le jeton doit être fourni par un autre mécanisme, tel que l'objet d'état ou capturé dans une lambda.

Quel est donc l'intérêt de fournir le jeton d'annulation dans le constructeur ?

262voto

Max Galkin Points 10116

Transmission d'un CancellationToken dans le Task l'associe à la tâche.

Citation Réponse de Stephen Toub de MSDN :

Cela présente deux avantages principaux :

  1. Si le jeton a fait l'objet d'une demande d'annulation avant le Task commence à s'exécuter, le Task ne s'exécute pas. Plutôt que de Running il passera immédiatement à Canceled . Cela permet d'éviter la coûts liés à l'exécution de la tâche si celle-ci devait de toute façon être annulée en cours d'exécution. de toute façon.
  2. Si le corps de la tâche surveille également le jeton d'annulation et lance un OperationCanceledException c (qui est ce que ThrowIfCancellationRequested ), puis lorsque le voit que OperationCanceledException il vérifie si le OperationCanceledException correspond au jeton de la tâche de la tâche. Si c'est le cas, cette exception est considérée comme une reconnaissance de la annulation de la coopération et la tâche Task transitions vers le Canceled (plutôt que l'état Faulted ).

27voto

user7116 Points 39829

Le constructeur utilise le jeton pour gérer l'annulation en interne. Si votre code souhaite accéder au jeton, il vous incombe de vous le transmettre. Je vous recommande vivement de lire la section Le livre Parallel Programming with Microsoft .NET (Programmation parallèle avec Microsoft .NET) sur CodePlex .

Exemple d'utilisation de CTS dans le livre :

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task myTask = Task.Factory.StartNew(() =>
{
    for (...)
    {
        token.ThrowIfCancellationRequested();

        // Body of for loop.
    }
}, token);

// ... elsewhere ...
cts.Cancel();

3 votes

Et que se passe-t-il si l'on ne passe pas le token comme paramètre ? Il semble que le comportement sera le même, sans objet.

2 votes

@sergdev : vous passez le jeton pour l'enregistrer avec la tâche et le planificateur. Ne pas le transmettre et l'utiliser serait un comportement non défini.

3 votes

@sergdev : après test : myTask.IsCanceled et myTask.Status ne sont pas les mêmes quand on ne passe pas le token en paramètre. Le statut sera failed au lieu de canceled. Néanmoins l'exception est la même : c'est une OperationCanceledException dans les deux cas.

7voto

x0n Points 26002

L'annulation n'est pas un cas aussi simple qu'on pourrait le croire. Certaines subtilités sont expliquées dans cet article de blog sur msdn :

Par exemple :

Dans certaines situations, dans le cadre des extensions parallèles et de l'i il est nécessaire de réveiller une méthode bloquée pour des raisons qui ne sont pas dues l'annulation explicite par un utilisateur. Par exemple, si un thread est bloqué sur blockingCollection.Take() en raison de la vacuité de la collection et qu'un autre thread souscrit à blockingCollection.CompleteAdding() alors le premier appel devrait réveiller se réveiller et lancer un InvalidOperationException pour représenter une erreur incorrecte.

Annulation dans les extensions parallèles

3voto

Eliahu Aaron Points 2033

Voici un exemple de code qui illustre les deux points de la méthode réponse acceptée par Max Galkin :

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Start canceled task, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        StartCanceledTaskTest(true);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, don't pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(false);
        Console.WriteLine();

        Console.WriteLine("*********************************************************************");
        Console.WriteLine("* Throw if cancellation requested, pass token to constructor");
        Console.WriteLine("*********************************************************************");
        ThrowIfCancellationRequestedTest(true);
        Console.WriteLine();

        Console.WriteLine();
        Console.WriteLine("Test Completed!!!");
        Console.ReadKey();
    }

    static void StartCanceledTaskTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, false), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, false));
        }

        Console.WriteLine("Canceling task");
        tokenSource.Cancel();

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void ThrowIfCancellationRequestedTest(bool passTokenToConstructor)
    {
        Console.WriteLine("Creating task");
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        Task task = null;
        if (passTokenToConstructor)
        {
            task = new Task(() => TaskWork(tokenSource.Token, true), tokenSource.Token);

        }
        else
        {
            task = new Task(() => TaskWork(tokenSource.Token, true));
        }

        try
        {
            Console.WriteLine("Starting task");
            task.Start();
            Thread.Sleep(100);

            Console.WriteLine("Canceling task");
            tokenSource.Cancel();
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: {0}", ex.Message);
            if (ex.InnerException != null)
            {
                Console.WriteLine("InnerException: {0}", ex.InnerException.Message);
            }
        }

        Console.WriteLine("Task.Status: {0}", task.Status);
    }

    static void TaskWork(CancellationToken token, bool throwException)
    {
        int loopCount = 0;

        while (true)
        {
            loopCount++;
            Console.WriteLine("Task: loop count {0}", loopCount);

            token.WaitHandle.WaitOne(50);
            if (token.IsCancellationRequested)
            {
                Console.WriteLine("Task: cancellation requested");
                if (throwException)
                {
                    token.ThrowIfCancellationRequested();
                }

                break;
            }
        }
    }
}

Sortie :

*********************************************************************
* Start canceled task, don't pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Task: loop count 1
Task: cancellation requested
Task.Status: RanToCompletion

*********************************************************************
* Start canceled task, pass token to constructor
*********************************************************************
Creating task
Canceling task
Starting task
Exception: Start may not be called on a task that has completed.
Task.Status: Canceled

*********************************************************************
* Throw if cancellation requested, don't pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: The operation was canceled.
Task.Status: Faulted

*********************************************************************
* Throw if cancellation requested, pass token to constructor
*********************************************************************
Creating task
Starting task
Task: loop count 1
Task: loop count 2
Canceling task
Task: cancellation requested
Exception: One or more errors occurred.
InnerException: A task was canceled.
Task.Status: Canceled

Test Completed!!!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X