web-dev-qa-db-fra.com

L'utilisation de CancellationToken pour le délai d'expiration dans Task.Run ne fonctionne pas

OK, mes questions sont vraiment simples. Pourquoi ce code ne jette pas TaskCancelledException?

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationTokenSource(500).Token).Result;

    Console.WriteLine(v); // this outputs 10 - instead of throwing error.
    Console.Read();
}

Mais celui-ci fonctionne

static void Main()
{
    var v = Task.Run(() =>
    {
        Thread.Sleep(1000);
        return 10;
    }, new CancellationToken(true).Token).Result;

    Console.WriteLine(v); // this one throws
    Console.Read();
}
24
Aliostad

Annulation dans les fils gérés :

L'annulation est coopérative et n'est pas imposée à l'auditeur. Le programme d'écoute détermine comment terminer en douceur en réponse à une demande d'annulation.

Vous n'avez écrit aucun code dans votre méthode Task.Run pour accéder à votre CancellationToken et pour implémenter une annulation - de sorte que vous avez effectivement ignoré la demande d'annulation et êtes allé jusqu'au bout.

30

Il y a une différence dans l'annulation d'une tâche en cours et une tâche dont l'exécution est planifiée.

Après l'appel de la méthode Task.Run, la tâche est uniquement planifiée et n'a probablement pas encore été exécutée.

Lorsque vous utilisez la famille de surcharges avec prise en charge de l'annulation Task.Run (..., CancellationToken), le jeton d'annulation est vérifié lorsque la tâche est sur le point de s'exécuter. Si IsCancellationRequested est défini sur true pour le jeton d'annulation, une exception du type TaskCanceledException est levée.

Si la tâche est déjà en cours d'exécution, il incombe à la tâche d'appeler la méthode ThrowIfCancellationRequested ou de simplement lever l'opération OperationCanceledException.

Selon MSDN, il ne s'agit que d'une méthode pratique pour les éléments suivants:

if (jeton.IsCancellationRequested) lancer une nouvelle OperationCanceledException (jeton);

Ce n'est pas le type d'exception utilisé dans ces deux cas:

catch (TaskCanceledException ex)
{
    // Task was canceled before running.
}
catch (OperationCanceledException ex)
{
    // Task was canceled while running.
}

Notez également que TaskCanceledException dérive de OperationCanceledException. Vous ne pouvez donc avoir qu'une seule clause catch pour le type OperationCanceledException:

catch (OperationCanceledException ex)
{
    if (ex is TaskCanceledException)
        // Task was canceled before running.
    // Task was canceled while running.
}
17
George Polevoy

Je pense que parce que vous n'invoquez pas la méthode ThrowIfCancellationRequested() à partir de votre objet CancellationToken ..__, vous ignorez ainsi la demande d'annulation de la tâche.

Vous devriez faire quelque chose comme ça:

void Main()
{
    var ct = new CancellationTokenSource(500).Token;
     var v = 
     Task.Run(() =>
    {
        Thread.Sleep(1000);
        ct.ThrowIfCancellationRequested();
        return 10;
    }, ct).Result;

    Console.WriteLine(v); //now a TaskCanceledException is thrown.
    Console.Read();
}

La deuxième variante de votre code fonctionne, car vous êtes déjà en train d'initialiser un jeton avec un état Canceled défini sur true. En effet, comme indiqué ici :

If canceled is true, both CanBeCanceled and IsCancellationRequested will be true

l'annulation a déjà été demandée et l'exception TaskCanceledException sera immédiatement levée, sans démarrer la tâche.

15
Alberto Solano

Une autre implémentation utilisant Task.Delay avec token à la place est Thread.Sleep. 

 static void Main(string[] args)
    {
        var task = GetValueWithTimeout(1000);
        Console.WriteLine(task.Result);
        Console.ReadLine();
    }

    static async Task<int> GetValueWithTimeout(int milliseconds)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;
        cts.CancelAfter(milliseconds);
        token.ThrowIfCancellationRequested();

        var workerTask = Task.Run(async () =>
        {
            await Task.Delay(3500, token);
            return 10;
        }, token);

        try
        {
            return await workerTask;
        }
        catch (OperationCanceledException )
        {
            return 0;
        }
    }
1
Z.R.T.