web-dev-qa-db-fra.com

Pourquoi TaskCanceledException se produit-il?

J'ai le code de test suivant:

void Button_Click(object sender, RoutedEventArgs e)
{
    var source = new CancellationTokenSource();

    var tsk1 = new Task(() => Thread1(source.Token), source.Token);
    var tsk2 = new Task(() => Thread2(source.Token), source.Token);

    tsk1.Start();
    tsk2.Start();

    source.Cancel();

    try
    {
        Task.WaitAll(new[] {tsk1, tsk2});
    }
    catch (Exception ex)
    {
        // here exception is caught
    }
}

void Thread1(CancellationToken token)
{
    Thread.Sleep(2000);

    // If the following line is enabled, the result is the same.
    // token.ThrowIfCancellationRequested();
}

void Thread2(CancellationToken token)
{
    Thread.Sleep(3000);
}

Dans les méthodes de thread, je ne lance aucune exception, mais j'obtiens TaskCanceledException dans le bloc try-catch Du code externe qui démarre les tâches. Pourquoi cela se produit et quel est le but de token.ThrowIfCancellationRequested(); dans ce cas. Je crois que l'exception ne devrait être levée que si j'appelle token.ThrowIfCancellationRequested(); dans la méthode du thread.

54
net_prog

Je crois que c'est un comportement attendu car vous rencontrez une variation d'une condition de course.

De Comment: annuler une tâche et ses enfants :

Le thread appelant ne termine pas la tâche de force; cela signale seulement que l'annulation est demandée. Si la tâche est déjà en cours d'exécution, il appartient au délégué utilisateur de noter la demande et de répondre de manière appropriée. Si l'annulation est demandée avant l'exécution de la tâche, le délégué utilisateur n'est jamais exécuté et l'objet de tâche passe à l'état Canceled.

et de Annulation de tâche :

Vous pouvez terminer l'opération en [...] revenant simplement du délégué. Dans de nombreux scénarios, cela suffit; cependant, une instance de tâche qui est "annulée" de cette manière passe à l'état RanToCompletion, pas à l'état Canceled.

Ma supposition éclairée ici est que pendant que vous appelez .Start() sur vos deux tâches, il est probable que l'une (ou les deux) n'ait pas réellement commencé avant que vous appeliez .Cancel() sur votre CancellationTokenSource. Je parie que si vous mettez au moins trois secondes d'attente entre le début des tâches et l'annulation, cela ne lèvera pas l'exception. Vous pouvez également vérifier le .Status propriété des deux tâches. Si j'ai raison, le .Status la propriété doit se lire TaskStatus.Canceled sur au moins l'un d'entre eux lorsque l'exception est levée.

N'oubliez pas que le démarrage d'un nouveau Task ne garantit pas la création d'un nouveau thread. Il appartient au TPL de décider ce qui obtient un nouveau thread et ce qui est simplement mis en file d'attente pour exécution.

24
Joshua