Partager via


Tâches de continuation

En programmation asynchrone, il est très courant pour une opération asynchrone, une fois terminée, d'appeler une deuxième opération et de lui passer des données. Habituellement, cela se fait à l'aide des méthodes de rappel. Dans la bibliothèque parallèle de tâches, les mêmes fonctionnalités sont fournies par les tâches de continuation. Une tâche de continuation (également appelée continuation) est une tâche asynchrone appelée par une autre tâche, également appelée antécédent lorsque ce dernier est terminé.

Les continuations sont relativement faciles à utiliser, mais sont néanmoins puissantes et souples. Par exemple, vous pouvez :

  • passer des données de l'antécédent à la continuation ;

  • spécifier les conditions précises sous lesquelles la continuation doit être ou non appelée ;

  • annuler une continuation avant qu'elle ne soit lancée ou pendant son exécution de manière coopérative ;

  • fournir des conseils sur la manière de planifier la continuation ;

  • appeler plusieurs continuations depuis le même antécédent ;

  • appeler une continuation lorsque certains ou tous les antécédents se terminent ;

  • chaîner des continuations les unes à la suite des autres à une longueur arbitraire ;

  • utiliser une continuation pour gérer des exceptions levées par l'antécédent.

Créez des continuations à l'aide de la méthode Task.ContinueWith. L'exemple suivant montre le modèle de base (pour plus de clarté, la gestion des exceptions a été omise).

' The antecedent task. Can also be created with Task.Factory.StartNew.
Dim taskA As Task(Of DayOfWeek) = New Task(Of DayOfWeek)(Function()
                                                             Return DateTime.Today.DayOfWeek
                                                         End Function)
' The continuation. Its delegate takes the antecedent task
' as an argument and can return a different type.
Dim continuation As Task(Of String) = taskA.ContinueWith(Function(antecedent)
                                                             Return String.Format("Today is {0}", antecedent.Result)
                                                         End Function)
' Start the antecedent.
taskA.Start()

' Use the contuation's result.
Console.WriteLine(continuation.Result)
            // The antecedent task. Can also be created with Task.Factory.StartNew.
            Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);

            // The continuation. Its delegate takes the antecedent task
            // as an argument and can return a different type.
            Task<string> continuation = taskA.ContinueWith((antecedent) =>
                {
                    return String.Format("Today is {0}.",
                                        antecedent.Result);
                });

            // Start the antecedent.
            taskA.Start();

            // Use the contuation's result.
            Console.WriteLine(continuation.Result);

Vous pouvez également créer une continuation multitâche qui s'exécutera lorsqu'un ou tous les tableaux de tâches seront terminés, comme indiqué dans l'exemple suivant.

Dim task1 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 34
                                                     End Function)

Dim task2 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 8
                                                     End Function)

Dim tasks() As Task(Of Integer) = {task1, task2}

Dim continuation = Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
                                                           Dim answer As Integer = tasks(0).Result + tasks(1).Result
                                                           Console.WriteLine("The answer is {0}", answer)


                                                       End Sub)
task1.Start()
task2.Start()
continuation.Wait()
            Task<int>[] tasks = new Task<int>[2];
            tasks[0] = new Task<int>(() =>
            {
                // Do some work... 
                return 34;
            });

            tasks[1] = new Task<int>(() =>
            {
                // Do some work...
                 return 8;
            });

            var continuation = Task.Factory.ContinueWhenAll(
                            tasks,
                            (antecedents) =>
                            {
                                int answer = tasks[0].Result + tasks[1].Result;
                                Console.WriteLine("The answer is {0}", answer);
                            });

            tasks[0].Start();
            tasks[1].Start();
            continuation.Wait();

Une continuation est créée dans l'état WaitingForActivation et par conséquent, peut être lancée uniquement par son antécédent de tâche. Appeler Task.Start sur une continuation dans du code utilisateur déclenche un System.InvalidOperationException.

Une continuation est une classe Task et ne bloque pas le thread sur lequel elle a démarré. Utilisez la méthode d'attente pour bloquer jusqu'à ce que la tâche de continuation se termine.

Options de continuation

Lorsque vous créez une continuation de tâche unique, vous pouvez utiliser une surcharge ContinueWith qui prend l'énumération System.Threading.Tasks.TaskContinuationOptions pour spécifier les conditions sous lesquelles l'antécédent de tâche doit lancer la continuation. Par exemple, vous pouvez spécifier que la continuation doit être exécutée uniquement si l'exécution de l'antécédent est terminée ou uniquement si l'antécédent s'est terminé avec un état d'erreur etc. Si cette condition n'est pas remplie lorsque l'antécédent est prêt à appeler la continuation, la continuation passe directement à l'état Canceled et ne peut plus être lancée. Si vous spécifiez l'une des options NotOn ou OnlyOn avec une continuation multitâche, une exception sera levée au moment de l'exécution.

L'énumération System.Threading.Tasks.TaskContinuationOptions inclut également les mêmes options que l'énumération System.Threading.Tasks.TaskCreationOptions. AttachedToParent, LongRunning et PreferFairness ont les mêmes significations et valeurs dans les deux types énumération. Ces options peuvent être utilisées avec des continuations multitâche.

Le tableau suivant énumère toutes les valeurs possibles dans TaskContinuationOptions.

Élément

Description

None

Spécifie le comportement par défaut lorsqu'aucun TaskContinuationOptions n'est spécifié. La continuation sera planifiée lorsque l'antécédent de tâche sera terminé, indépendamment de l'état final de ce dernier. Si la tâche est une tâche enfant, elle est créée en tant que tâche imbriquée détachée.

PreferFairness

Spécifie que la continuation doit être planifiée afin que les tâches planifiées précédemment soient susceptibles d'être exécutées plus tôt et que les tâches planifiées ultérieurement soient susceptibles d'être exécutées plus tard.

LongRunning

Spécifie que la continuation sera une opération de longue durée et de granulosité grossière. Conseille au System.Threading.Tasks.TaskScheduler de garantir le surabonnement.

AttachedToParent

Spécifie que la continuation, s'il s'agit d'une tâche enfant, est jointe à un parent dans la hiérarchie des tâches. La continuation est une tâche enfant uniquement si son antécédent est également une tâche enfant.

NotOnRanToCompletion

Spécifie que la continuation ne doit pas être planifiée si son antécédent s'est terminé.

NotOnFaulted

Spécifie que la continuation ne doit pas être planifiée si son antécédent a levé une exception non gérée.

NotOnCanceled

Spécifie que la continuation ne doit pas être planifiée si son antécédent a été annulé.

OnlyOnRanToCompletion

Spécifie que la continuation ne doit être planifiée que si son antécédent s'est terminé.

OnlyOnFaulted

Spécifie que la continuation ne doit être planifiée que si son antécédent a levé une exception non gérée. Lorsque vous utilisez l'option OnlyOnFaulted, il est garanti que la propriété Exception de l'antécédent n'est pas null. Vous pouvez utiliser cette propriété pour intercepter l'exception et voir quelle exception a provoqué l'erreur de la tâche. Si vous n'accédez pas à la propriété Exception, l'exception ne sera pas gérée. Aussi, si vous essayez d'accéder à la propriété Result d'une tâche qui été annulée ou a rencontré une erreur, une nouvelle exception sera déclenchée.

OnlyOnCanceled

Spécifie que la tâche de continuation doit être planifiée uniquement si son antécédent s'est terminé avec l'état Canceled.

ExecuteSynchronously

Pour les continuations de très courte durée. Spécifie que la continuation, dans l'idéal, doit être exécutée sur le thread qui provoque le passage de l'antécédent à son état final. Si l'antécédent est déjà terminé lors de la création de la continuation, le système tentera d'exécuter la continuation sur le thread qui crée la continuation. Si l'antécédent CancellationTokenSource est supprimé dans un bloc finally (Finally en Visual Basic), une continuation avec cette option s'exécutera dans ce bloc finally.

Passage de données à une continuation

Une référence à l'antécédent est passée au délégué d'utilisateur de la continuation en tant qu'argument. Si l'antécédent est un System.Threading.Tasks.Task<TResult> et que l'exécution de la tâche est terminée, la continuation peut accéder à la propriété Task<TResult>.Result de la tâche. Avec une continuation multitâche et la méthode Task.WaitAll, l'argument est le tableau d'antécédents. Lorsque vous utilisez Task.WaitAny, l'argument est le premier antécédent qui s'est terminé.

Task<TResult>.Result se bloque jusqu'à ce que la tâche soit terminée. Toutefois, si la tâche a été annulée ou a rencontré une erreur, Result lèvera une exception lorsque votre code essaiera d'y accéder. Vous pouvez éviter ce problème à l'aide de l'option OnlyOnRanToCompletion, comme indiqué dans l'exemple suivant.

            Dim aTask = Task(Of Integer).Factory.StartNew(Function()
                                                              Return 54
                                                          End Function)
            Dim bTask = aTask.ContinueWith(Sub(antecedent)
                                               Console.WriteLine("continuation {0}", antecedent.Result)
                                           End Sub,
                                           TaskContinuationOptions.OnlyOnRanToCompletion)

var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
},
    TaskContinuationOptions.OnlyOnRanToCompletion);

Si vous voulez que la continuation s'exécute même si l'exécution de l'antécédent n'est pas terminée, vous devez vous attendre à des exceptions. Une approche possible est de tester l'état de l'antécédent et d'essayer uniquement d'accéder à Result si l'état n'est ni Faulted ni Canceled. Vous pouvez également étudier la propriété Exception de l'antécédent. Pour plus d'informations, consultez Gestion des exceptions (bibliothèque parallèle de tâches).

Annulation d'une continuation

Une continuation rentre dans l'état Canceled dans les scénarios suivants :

  • Lorsqu'elle lève un OperationCanceledException en réponse à une requête d'annulation. Comme pour toute tâche, si l'exception contient le même jeton qui a été passé à la continuation, il est traité comme un accusé de réception de l'annulation coopérative.

  • Lorsqu'un System.Threading.CancellationToken est passé à une continuation en tant qu'argument et que la propriété IsCancellationRequested du jeton est true (True) avant l'exécution de la continuation. Dans un tel cas, la continuation n'est pas lancée et passe directement à l'état Canceled.

  • Lorsque la continuation ne s'exécute jamais parce que la condition définie dans ses TaskContinuationOptions n'a pas été remplie. Par exemple, si une tâche rentre dans un état Faulted, sa continuation créée par l'option NotOnFaulted passera à l'état Canceled et ne fonctionnera pas.

Pour empêcher une continuation de s'exécuter si son antécédent est annulé, spécifiez l'option NotOnCanceled lorsque vous créez la continuation.

Si une tâche et sa continuation représentent deux parties de la même opération logique, vous pouvez passer le même jeton d'annulation aux deux tâches, comme indiqué dans l'exemple suivant.

Dim someCondition As Boolean = True
Dim cts As New CancellationTokenSource
Dim task1 = New Task(Sub()
                         Dim ct As CancellationToken = cts.Token
                         While someCondition = True
                             ct.ThrowIfCancellationRequested()
                             ' Do the work here...
                             ' ...
                         End While
                     End Sub,
                     cts.Token
                     )

Dim task2 = task1.ContinueWith(Sub(antecedent)
                                   Dim ct As CancellationToken = cts.Token
                                   While someCondition = True
                                       ct.ThrowIfCancellationRequested()
                                       ' Do the work here
                                       ' ...
                                   End While
                               End Sub,
                               cts.Token)
task1.Start()
' ...
' Antecedent and/or continuation will
' respond to this request, depending on when it is made.
cts.Cancel()
Task task = new Task(() =>
{
    CancellationToken ct = cts.Token;
    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token
    );

Task task2 = task.ContinueWith((antecedent) =>
{
    CancellationToken ct = cts.Token;

    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token);

task.Start();
//...

// Antecedent and/or continuation will 
// respond to this request, depending on when it is made.
cts.Cancel();

Si l'antécédent n'a pas été annulé, le jeton peut toujours être utilisé pour annuler la continuation. Si l'antécédent a été annulé, la continuation ne sera pas lancée.

Une fois la continuation à l'état Canceled, elle peut affecter les continuations suivantes, selon les TaskContinuationOptions spécifiés pour ces continuations.

Les continuations supprimées ne seront pas lancées.

Continuations et tâches enfants

Une continuation ne s'exécute pas tant que l'antécédent et toutes ses tâches enfants attachées ne sont pas terminées. La continuation n'attend pas que les tâches enfants détachées soient terminées. L'état final de l'antécédent de tâche dépend de l'état final des tâches enfants attachées. L'état des tâches enfants détachées n'affectent pas le parent. Pour plus d'informations, consultez Tâches imbriquées et tâches enfants.

Gestion d'exceptions levées par des continuations

Une relation antécédent-continuation n'est pas une relation parent-enfant. Les exceptions levées par les continuations ne sont pas propagées à l'antécédent. Par conséquent, vous pouvez gérer les exceptions levées par les continuations de la même manière qu'avec une autre tâche, comme suit.

  1. Utilisez la méthode Wait, WaitAll ou WaitAny, ou leur équivalent générique, pour attendre la continuation. Vous pouvez attendre un antécédent et ses continuations dans la même instruction try (Try en Visual Basic), comme indiqué dans l'exemple suivant.
Dim task1 = Task(Of Integer).Factory.StartNew(Function()
                                                  Return 54
                                              End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
                                          Console.WriteLine("continuation {0}", antecedent.Result)
                                          Throw New InvalidOperationException()
                                      End Sub)

Try
    task1.Wait()
    continuation.Wait()
Catch ae As AggregateException
    For Each ex In ae.InnerExceptions
        Console.WriteLine(ex.Message)
    Next
End Try

Console.WriteLine("Exception handled. Let's move on.")
var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
    throw new InvalidOperationException();
});

try
{
    t.Wait();
    c.Wait();
}

catch (AggregateException ae)
{
    foreach(var e in ae.InnerExceptions)
        Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
  1. Utilisez une deuxième continuation pour observer la propriété Exception de la première continuation. Pour plus d'informations, consultez Gestion des exceptions (bibliothèque parallèle de tâches) et Comment : gérer les exceptions levées par des tâches.

  2. Si la continuation est une tâche enfant et a été créée à l'aide de l'option AttachedToParent, ses exceptions seront repropagées par le parent au thread appelant, comme c'est le cas pour tout autre enfant attaché. Pour plus d'informations, consultez Tâches imbriquées et tâches enfants.

Voir aussi

Concepts

Bibliothèque parallèle de tâches

Historique des modifications

Date

Historique

Motif

Juin 2010

Remarque ajoutée à propos du comportement asynchrone des continuations.

Commentaires client.