Programmation asynchrone basée sur les tâches

La bibliothèque parallèle de tâches (TPL) est basée sur le concept de tâche, qui représente une opération asynchrone. À certains égards, une tâche ressemble à un thread ou à un élément de travail ThreadPool, mais à un niveau d’abstraction supérieur. Le terme parallélisme des tâches fait référence à une ou plusieurs tâches indépendantes qui s’exécutent simultanément. Les tâches présentent deux grands avantages :

  • Une utilisation plus efficace et évolutive des ressources système.

    En arrière-plan, les tâches sont mises en file d’attente dans le ThreadPool, amélioré au moyen d’algorithmes qui déterminent le nombre de threads (et s’y ajustent) et qui fournissent l’équilibrage de charge afin d’optimiser le débit. Cela rend les tâches relativement simples et vous permet d’en créer de nombreuses pour un parallélisme affiné.

  • Davantage de contrôle par programmation qu'avec un thread ou un élément de travail.

    Les tâches et l'infrastructure construites autour de ces algorithmes fournissent un ensemble riche d'API prenant en charge l'attente, l'annulation, les continuations, la gestion fiable des exceptions, l'état détaillé, la planification personnalisée, et bien plus encore.

Pour ces deux raisons, TPL est l’API préférée pour l’écriture de code multithread, asynchrone et parallèle dans .NET.

Création et exécution implicites de tâches

La méthode Parallel.Invoke offre un moyen pratique d'exécuter simultanément un nombre d'instructions arbitraires. Pour cela, passez un délégué Action pour chaque élément de travail. La façon la plus facile de créer ces délégués est d’utiliser des expressions lambda. L’expression lambda peut appeler une méthode nommée ou fournir le code inline. L'exemple suivant montre un appel Invoke de base qui crée et démarre deux tâches qui s'exécutent simultanément. La première tâche est représentée par une expression lambda qui appelle une méthode nommée DoSomeWork et la seconde tâche est représentée par une expression lambda qui appelle une méthode nommée DoSomeOtherWork.

Notes

Cette documentation utilise les expressions lambda pour définir les délégués de la bibliothèque parallèle de tâches. Si les expressions lambda en C# ou Visual Basic ne vous sont pas familières, consultez la page Expressions lambda en PLINQ et dans la bibliothèque parallèle de tâches.

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

Notes

Le nombre d'instances Task créées en arrière-plan par Invoke n'est pas nécessairement égal au nombre de délégués fournis. La bibliothèque parallèle de tâches peut utiliser différentes optimisations, surtout avec un grand nombre de délégués.

Pour plus d’informations, consultez Comment : utiliser parallel_invoke pour exécuter des opérations parallèles.

Pour un plus grand contrôle de l’exécution de tâches ou pour retourner une valeur à partir de la tâche, vous devez utiliser les objets Task de manière plus explicite.

Création et exécution explicites de tâches

Une tâche qui ne retourne pas de valeur est représentée par la classe System.Threading.Tasks.Task. Une tâche qui retourne une valeur est représentée par la classe System.Threading.Tasks.Task<TResult>, qui hérite de Task. L’objet de tâche gère les détails de l’infrastructure et fournit des méthodes et des propriétés accessibles depuis le thread appelant pendant la durée de vie de la tâche. Par exemple, vous pouvez accéder à la propriété Status d'une tâche à tout moment pour déterminer si son exécution a commencé, est terminée, a été annulée ou a levé une exception. Le statut est représenté par une énumération TaskStatus.

Lorsque vous créez une tâche, vous lui donnez un délégué utilisateur qui encapsule le code que la tâche exécutera. Le délégué peut être exprimé en tant que délégué nommé, méthode anonyme ou expression lambda. Les expressions lambda peuvent contenir un appel à une méthode nommée, comme indiqué dans l’exemple suivant. Notez que l’exemple inclut un appel à la méthode Task.Wait pour garantir que l’exécution de la tâche se termine avant la fin de l’application en mode console.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression.
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Thread.CurrentThread.Name = "Main"

        ' Create a task and supply a user delegate by using a lambda expression. 
        Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
        ' Start the task.
        taskA.Start()

        ' Output a message from the calling thread.
        Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name)
        taskA.Wait()
    End Sub
End Module
' The example displays output like the following:
'    Hello from thread 'Main'.
'    Hello from taskA.

Vous pouvez également utiliser les méthodes Task.Run pour créer et lancer une tâche dans une opération. Pour gérer la tâche, les méthodes Run utilisent le planificateur de tâches par défaut, quel que soit le planificateur de tâches associé au thread actuel. Les méthodes Run représentent la meilleure façon de créer et de lancer des tâches lorsqu’un plus grand contrôle sur la création et la planification de la tâche n’est pas nécessaire.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Thread.CurrentThread.Name = "Main"

        Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

        ' Output a message from the calling thread.
        Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name)
        taskA.Wait()
    End Sub
End Module
' The example displays output like the following:
'    Hello from thread 'Main'.
'    Hello from taskA.

Vous pouvez également utiliser la méthode TaskFactory.StartNew pour créer et lancer une tâche dans une opération. Utilisez cette méthode quand la création et la planification n’ont pas besoin d’être séparées et que vous avez besoin d’options supplémentaires de création de tâches ou d’utiliser un planificateur spécifique, ou lorsque vous devez obtenir l’état supplémentaire dans la tâche via sa propriété Task.AsyncState, comme indiqué dans l’exemple suivant.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)

        For Each task In taskArray
            Dim data = TryCast(task.AsyncState, CustomData)
            If data IsNot Nothing Then
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum)
            End If
        Next
    End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Task et Task<TResult> exposent tous deux une propriété Factory statique qui retourne une instance par défaut de TaskFactory, afin que vous puissiez appeler la méthode en tant que Task.Factory.StartNew(). De plus, dans l’exemple suivant, parce que les tâches sont de type System.Threading.Tasks.Task<TResult>, chacune d’elle a une propriété Task<TResult>.Result publique contenant le résultat du calcul. Les tâches sont exécutées de façon asynchrone et peuvent se terminer dans n’importe quel ordre. Si la propriété Result est accessible avant la fin du calcul, la propriété bloque le thread appelant jusqu'à ce que la valeur soit disponible.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;

        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i],
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum;
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                            Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
                            Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

        Dim results(taskArray.Length - 1) As Double
        Dim sum As Double

        For i As Integer = 0 To taskArray.Length - 1
            results(i) = taskArray(i).Result
            Console.Write("{0:N1} {1}", results(i),
                              If(i = taskArray.Length - 1, "= ", "+ "))
            sum += results(i)
        Next
        Console.WriteLine("{0:N1}", sum)
    End Sub

    Private Function DoComputation(start As Double) As Double
        Dim sum As Double
        For value As Double = start To start + 10 Step .1
            sum += value
        Next
        Return sum
    End Function
End Module
' The example displays the following output:
'       606.0 + 10,605.0 + 100,495.0 = 111,706.0

Pour plus d’informations, consultez Comment : retourner une valeur à partir d’une tâche.

Lorsque vous utilisez une expression lambda pour créer le délégué d’une tâche, vous avez accès à toutes les variables qui sont visibles à ce stade dans votre code source. Toutefois, dans certains cas, notamment dans les boucles, une expression lambda ne capture pas la variable comme prévu. Elle capture uniquement la valeur finale, pas la valeur qui change au cours de chaque itération. L'exemple de code suivant illustre le problème. Il passe un compteur de boucles à une expression lambda qui instancie un objet CustomData et utilise le compteur de boucles comme identificateur de l'objet. Comme le montre la sortie de l'exemple, chaque objet CustomData a un identificateur identique.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        ' Create the task object by using an Action(Of Object) to pass in the loop
        ' counter. This produces an unexpected result.
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks}
                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                     Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                       data.Name, data.CreationTime, data.ThreadNum)
                                                 End Sub,
                                                 i)
        Next
        Task.WaitAll(taskArray)
    End Sub
End Module
' The example displays output like the following:
'       Task #10 created at 635116418427727841 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427727841 on thread #3.
'       Task #10 created at 635116418427747843 on thread #3.
'       Task #10 created at 635116418427747843 on thread #3.
'       Task #10 created at 635116418427737842 on thread #4.

Vous pouvez accéder à la valeur de chaque itération en fournissant un objet d’état à une tâche via son constructeur. L'exemple suivant modifie l'exemple précédent en utilisant le compteur de boucles lors de la création de l'objet CustomData qui, à son tour, est passé à l'expression lambda. Comme le montre la sortie de l'exemple, chaque objet CustomData possède maintenant un identificateur unique basé sur la valeur du compteur de boucle au moment où l'objet a été instancié.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        ' Create the task object by using an Action(Of Object) to pass in custom data
        ' to the Task constructor. This is useful when you need to capture outer variables
        ' from within a loop. 
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                     Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                       data.Name, data.CreationTime, data.ThreadNum)
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)
    End Sub
End Module
' The example displays output like the following:
'       Task #0 created at 635116412924597583 on thread #3.
'       Task #1 created at 635116412924607584 on thread #4.
'       Task #3 created at 635116412924607584 on thread #4.
'       Task #4 created at 635116412924607584 on thread #4.
'       Task #2 created at 635116412924607584 on thread #3.
'       Task #6 created at 635116412924607584 on thread #3.
'       Task #5 created at 635116412924607584 on thread #4.
'       Task #8 created at 635116412924607584 on thread #4.
'       Task #7 created at 635116412924607584 on thread #3.
'       Task #9 created at 635116412924607584 on thread #4.

Cet état est passé comme argument au délégué de tâche et est accessible dans l'objet de tâche à l'aide de la propriété Task.AsyncState. L'exemple suivant est une variante de l'exemple précédent. Il utilise la propriété AsyncState pour afficher des informations sur les objets CustomData passés à l’expression lambda.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)

        For Each task In taskArray
            Dim data = TryCast(task.AsyncState, CustomData)
            If data IsNot Nothing Then
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum)
            End If
        Next
    End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

ID de la tâche

Chaque tâche reçoit un ID d'entier qui l'identifie de façon unique dans un domaine d'application et peut être accessible à l'aide de la propriété Task.Id. L’ID est utile pour la consultation des informations de tâche dans les fenêtres Piles parallèles et Tâches du débogueur Visual Studio. L’ID est créé de manière différée, ce qui signifie qu’il n’est pas créé tant que cela n’est pas demandé ; par conséquent, une tâche peut avoir un ID différent à chaque exécution du programme. Pour plus d’informations sur l’affichage des ID de tâche dans le débogueur, consultez Utilisation de la fenêtre Tâches et Utilisation de la fenêtre Piles parallèles.

Options de création de tâches

La plupart des API qui créent des tâches fournissent des surcharges qui acceptent un paramètre TaskCreationOptions. En spécifiant une ou plusieurs de ces options, vous indiquez au planificateur de tâches comment planifier la tâche sur le pool de threads. Les options peuvent être combinées à l’aide d’une opération or au niveau du bit.

L’exemple suivant montre une tâche avec les LongRunning options et PreferFairness .

var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

Tâches, threads et culture

Chaque thread a une culture associée et une culture d’interface utilisateur, qui sont définies par les Thread.CurrentCulture Thread.CurrentUICulture Propriétés et, respectivement. La culture d'un thread est utilisée dans des opérations telles que la mise en forme, l'analyse, le tri et la comparaison de chaînes. La culture d'interface utilisateur d'un thread est utilisée dans la recherche de ressources.

À moins que vous ne spécifiiez une culture par défaut pour tous les threads d’un domaine d’application à l’aide des CultureInfo.DefaultThreadCurrentCulture CultureInfo.DefaultThreadCurrentUICulture Propriétés et, la culture par défaut et la culture d’interface utilisateur d’un thread sont définies par la culture du système. Si vous définissez explicitement la culture d'un thread et lancez un nouveau thread, ce dernier n'hérite pas de la culture du thread appelant ; au lieu de cela, sa culture est la culture du système par défaut. Toutefois, dans la programmation basée sur les tâches, les tâches utilisent la culture du thread appelant, même si la tâche s’exécute de façon asynchrone sur un thread différent.

L'exemple suivant illustre cette situation de façon simple. Elle modifie la culture actuelle de l’application en français (France) (ou, si français (France) est déjà la culture actuelle, en anglais (États-Unis)). Il appelle ensuite un délégué nommé formatDelegate qui retourne des nombres mis en forme en tant que valeurs de devise dans la nouvelle culture. Si le délégué est appelé par une tâche de façon synchrone ou asynchrone, la tâche utilise la culture du thread appelant.

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

Notes

Dans les versions de .NET Framework antérieures à .NET Framework 4,6, la culture d’une tâche est déterminée par la culture du thread sur lequel elle s’exécute, et non par la culture du thread appelant. Pour les tâches asynchrones, cela signifie que la culture utilisée par la tâche peut être différente de la culture du thread appelant.

Pour plus d'informations sur les tâches asynchrones et la culture, consultez la section « Culture et opérations asynchrones basées sur les tâches » dans la rubrique CultureInfo.

Création de continuations de tâches

Les méthodes Task.ContinueWith et Task<TResult>.ContinueWith vous permettent de spécifier une tâche à démarrer lorsque l’antécédent est terminé. Une référence à la tâche antécédente est passée au délégué de la tâche de continuation afin qu’il puisse examiner l’état de la tâche antécédente et, en récupérant la valeur de la propriété Task<TResult>.Result, utiliser la sortie de l’antécédent comme entrée pour la continuation.

Dans l'exemple suivant, la tâche getData est démarrée par un appel à la méthode TaskFactory.StartNew<TResult>(Func<TResult>). La tâche processData démarre automatiquement lorsque getData est terminé, et displayData commence lorsque processData est terminé. getData produit un tableau d'entiers, accessible à la tâche processData via la propriété getData de la tâche Task<TResult>.Result. La tâche processData traite ce tableau et retourne un résultat dont le type est déduit du type de retour de l'expression lambda passée à la méthode Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). La tâche displayData s'exécute automatiquement lorsque processData est terminé, et l'objet Tuple<T1,T2,T3> retourné par l'expression lambda processData est accessible à la tâche displayData via la propriété processData de la tâche Task<TResult>.Result. La tâche displayData prend le résultat de la tâche processData et produit un résultat dont le type est déduit de façon semblable et mis à disposition du programme dans la propriété Result.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var getData = Task.Factory.StartNew(() => {
                                             Random rnd = new Random();
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } );
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2,
                                                                         x.Result.Item3);
                                                 } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim getData = Task.Factory.StartNew(Function()
                                                Dim rnd As New Random()
                                                Dim values(99) As Integer
                                                For ctr = 0 To values.GetUpperBound(0)
                                                    values(ctr) = rnd.Next()
                                                Next
                                                Return values
                                            End Function)
        Dim processData = getData.ContinueWith(Function(x)
                                                   Dim n As Integer = x.Result.Length
                                                   Dim sum As Long
                                                   Dim mean As Double

                                                   For ctr = 0 To x.Result.GetUpperBound(0)
                                                       sum += x.Result(ctr)
                                                   Next
                                                   mean = sum / n
                                                   Return Tuple.Create(n, sum, mean)
                                               End Function)
        Dim displayData = processData.ContinueWith(Function(x)
                                                       Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                            x.Result.Item1, x.Result.Item2,
                                                                            x.Result.Item3)
                                                   End Function)
        Console.WriteLine(displayData.Result)
    End Sub
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Comme Task.ContinueWith est une méthode d‘instance, vous pouvez concaténer les appels de méthode ensemble au lieu d‘instancier un objet Task<TResult> pour chaque antécédent. L'exemple suivant est fonctionnellement identique à l'exemple précédent, sauf qu'il chaîne ensemble les appels à la méthode Task.ContinueWith. Notez que l'objet Task<TResult> retourné par la chaîne d'appels de méthode est la tâche de continuation finale.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var displayData = Task.Factory.StartNew(() => {
                                                 Random rnd = new Random();
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ).
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,
                                                             x.Result.Item3);
                                     } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim displayData = Task.Factory.StartNew(Function()
                                                    Dim rnd As New Random()
                                                    Dim values(99) As Integer
                                                    For ctr = 0 To values.GetUpperBound(0)
                                                        values(ctr) = rnd.Next()
                                                    Next
                                                    Return values
                                                End Function). _
                          ContinueWith(Function(x)
                                           Dim n As Integer = x.Result.Length
                                           Dim sum As Long
                                           Dim mean As Double

                                           For ctr = 0 To x.Result.GetUpperBound(0)
                                               sum += x.Result(ctr)
                                           Next
                                           mean = sum / n
                                           Return Tuple.Create(n, sum, mean)
                                       End Function). _
                          ContinueWith(Function(x)
                                           Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                              x.Result.Item1, x.Result.Item2,
                                                              x.Result.Item3)
                                       End Function)
        Console.WriteLine(displayData.Result)
    End Sub
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Les méthodes ContinueWhenAll et ContinueWhenAny vous permettent de continuer avec plusieurs tâches.

Pour plus d’informations, consultez Chaînage des tâches à l’aide de tâches de continuation.

Création de tâches enfants détachées

Lorsque le code utilisateur qui s'exécute dans une tâche crée une tâche sans spécifier l'option AttachedToParent, la nouvelle tâche n'est pas synchronisée avec la tâche parent externe. Ce type de tâche non synchronisée est appelé tâche imbriquée détachée ou tâche enfant détachée. L’exemple suivant montre une tâche qui crée une tâche enfant détachée.

var outer = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Outer task beginning.");

    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000000);
        Console.WriteLine("Detached task completed.");
    });
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

Notez que la tâche parent n’attend pas que la tâche enfant détachée soit terminée.

Création de tâches enfants

Lorsque le code utilisateur qui s’exécute dans une tâche crée une tâche avec l' AttachedToParent option, la nouvelle tâche est appelée tâche enfant attachée de la tâche parente. Vous pouvez utiliser l’option AttachedToParent pour exprimer le parallélisme des tâches structuré, car la tâche parent attend implicitement que toutes les tâches enfants attachées soient terminées. L’exemple suivant affiche une tâche parent qui crée dix tâches enfants attachées. Notez que l'exemple appelle la méthode Task.Wait pour attendre la fin de la tâche parente, il ne doit pas explicitement attendre la fin des tâches enfants attachées.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.",
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim parent = Task.Factory.StartNew(Sub()
                                               Console.WriteLine("Parent task beginning.")
                                               For ctr As Integer = 0 To 9
                                                   Dim taskNo As Integer = ctr
                                                   Task.Factory.StartNew(Sub(x)
                                                                             Thread.SpinWait(5000000)
                                                                             Console.WriteLine("Attached child #{0} completed.",
                                                                                               x)
                                                                         End Sub,
                                                                         taskNo, TaskCreationOptions.AttachedToParent)
                                               Next
                                           End Sub)
        parent.Wait()
        Console.WriteLine("Parent task completed.")
    End Sub
End Module
' The example displays output like the following:
'       Parent task beginning.
'       Attached child #9 completed.
'       Attached child #0 completed.
'       Attached child #8 completed.
'       Attached child #1 completed.
'       Attached child #7 completed.
'       Attached child #2 completed.
'       Attached child #6 completed.
'       Attached child #3 completed.
'       Attached child #5 completed.
'       Attached child #4 completed.
'       Parent task completed.

Une tâche parent peut utiliser l’option TaskCreationOptions.DenyChildAttach pour empêcher d’autres tâches de s’attacher à la tâche parent. Pour plus d'informations, consultez Tâches enfants attachées et détachées.

Attente de fin d’exécution de tâches

Les types System.Threading.Tasks.Task et System.Threading.Tasks.Task<TResult> fournissent plusieurs surcharges des méthodes Task.Wait qui vous permettent d’attendre qu’une tâche soit terminée. De plus, les surcharges des méthodes statiques Task.WaitAll et Task.WaitAny vous permettent d’attendre que certaines ou toutes les tâches soient terminées.

En général, il est nécessaire d’attendre une tâche pour l’une des raisons suivantes :

  • Le thread principal dépend du résultat final calculé par une tâche.

  • Vous devez gérer les exceptions pouvant être levées depuis la tâche.

  • L'application peut se fermer avant que l'exécution de toutes les tâches ne soit terminée. Par exemple, les applications de console s'arrêtent dès que l'intégralité du code synchrone dans Main (point d'entrée de l'application) est exécutée.

L’exemple suivant affiche le modèle de base qui n’implique pas la gestion des exceptions.

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

Pour obtenir un exemple illustrant la gestion des exceptions, consultez Gestion des exceptions.

Certaines surcharges vous permettent de spécifier un délai d'attente, et d'autres prennent un CancellationToken supplémentaire en tant que paramètre d'entrée, afin que l'attente puisse être annulée soit par programmation, soit en réponse à l'entrée utilisateur.

Lorsque vous attendez une tâche, vous attendez implicitement tous les enfants de cette tâche créés à l'aide de l'option TaskCreationOptions.AttachedToParent. Task.Wait est retourné immédiatement si la tâche est déjà terminée. Toutes les exceptions déclenchées par une tâche seront levées par une méthode Task.Wait, même si la méthode Task.Wait a été appelée une fois la tâche terminée.

Composition des tâches

Les classes Task et Task<TResult> fournissent plusieurs méthodes qui peuvent vous aider à composer plusieurs tâches pour implémenter des modèles courants et améliorer l’utilisation des fonctionnalités de langage asynchrones fournies par C#, Visual Basic et F#. Cette section décrit les méthodes WhenAll, WhenAny, Delay et FromResult.

Task.WhenAll

La méthode Task.WhenAll attend de façon asynchrone que plusieurs objets Task ou Task<TResult> se terminent. Elle fournit des versions surchargées qui vous permettent d’attendre des ensembles de tâches non-uniformes. Par exemple, vous pouvez attendre que plusieurs objets Task et Task<TResult> soient terminés à partir d'un appel de méthode.

Task.WhenAny

La méthode Task.WhenAny attend de façon asynchrone qu'un des objets Task ou Task<TResult> se termine. À l’instar de la méthode Task.WhenAll, cette méthode fournit des versions surchargées qui vous permettent d’attendre des jeux de tâches non-uniformes. La méthode WhenAny est spécialement utile dans les scénarios suivants.

  • Opérations redondantes. Considérez un algorithme ou une opération pouvant être exécutée plusieurs façons. Vous pouvez utiliser la méthode WhenAny pour sélectionner l'opération qui se termine en premier et annuler les opérations restantes.

  • Opérations entrelacées. Vous pouvez démarrer plusieurs opérations qui doivent toutes se terminer et utiliser la méthode WhenAny pour traiter les résultats à mesure que chaque opération se termine. Lorsqu’une opération se termine, vous pouvez démarrer une ou plusieurs autres tâches.

  • Opérations limitées. Vous pouvez utiliser la méthode WhenAny pour étendre le scénario précédent en limitant le nombre d'opérations simultanées.

  • Opérations expirées. Vous pouvez utiliser la méthode WhenAny pour sélectionner entre une ou plusieurs tâches et une tâche qui se termine après une heure spécifique, telle qu’une tâche retournée par la méthode Delay. La méthode Delay est décrite dans la section suivante.

Task.Delay

La méthode Task.Delay produit un objet Task qui se termine après le délai spécifié. Vous pouvez utiliser cette méthode pour générer des boucles qui, occasionnellement, sondent les données, introduisent des délais, diffèrent la gestion des entrées d'utilisateur pendant une durée prédéterminé, et ainsi de suite.

Task(T).FromResult

En utilisant la méthode Task.FromResult, vous pouvez créer un objet Task<TResult> qui contient un résultat précalculé. Cette méthode est utile lorsque vous exécutez une opération asynchrone qui retourne un objet Task<TResult>, et que le résultat de cet objet Task<TResult> est déjà calculé. Pour obtenir un exemple qui utilise FromResult pour récupérer les résultats des opérations de téléchargement asynchrones qui sont conservées dans un cache, consultez Procédure : création de tâches précalculées.

Gestion des exceptions des tâches

Lorsqu’une tâche lève une ou plusieurs exceptions, les exceptions sont encapsulées dans une exception AggregateException. Cette exception est propagée vers le thread joint à la tâche, qui est en général le thread qui attend la fin de la tâche, ou le thread qui accède à la propriété Result. Ce comportement permet d'appliquer la stratégie .NET Framework selon laquelle toutes les exceptions non gérées doivent par défaut détruire le processus. Le code appelant peut gérer les exceptions en utilisant l’un des éléments suivants dans un bloc try/catch :

Le thread joint peut également gérer des exceptions en accédant à la propriété Exception avant que la tâche ne soit récupérée par le garbage collector. En accédant à cette propriété, vous empêchez l'exception non gérée de déclencher le comportement de propagation de l'exception qui termine le processus lorsque l'objet est finalisé.

Pour plus d’informations sur les exceptions et les tâches, consultez Gestion des exceptions.

Annulation des tâches

La classe Task prend en charge l'annulation coopérative et s'intègre pleinement aux classes System.Threading.CancellationTokenSource et System.Threading.CancellationToken, qui ont été introduites dans .NET Framework 4. De nombreux constructeurs de la classe System.Threading.Tasks.Task prennent un objet CancellationToken en tant que paramètre d'entrée. La plupart des surcharges StartNew et Run incluent également un paramètre CancellationToken.

Vous pouvez créer le jeton et la requête d'annulation ultérieurement, à l'aide de la classe CancellationTokenSource. Passez le jeton au Task en tant qu'argument et référencez ce même jeton dans votre délégué d'utilisateur, qui répond à une requête d'annulation.

Pour plus d’informations, consultez Annulation de tâches et Comment : annuler une tâche et ses enfants.

Classe TaskFactory

La classe TaskFactory fournit des méthodes statiques qui encapsulent des modèles communs pour la création et le lancement des tâches et des tâches de continuation.

TaskFactory par défaut est accessible en tant que propriété statique dans la classe Task ou Task<TResult>. Vous pouvez également instancier directement un TaskFactory et spécifier différentes options qui incluent une option CancellationToken, TaskCreationOptions, TaskContinuationOptions ou TaskScheduler. Toutes les options spécifiées lors de la création de la fabrique de tâches seront appliquées à toutes les tâches qu'elle crée, à moins que la tâche Task ne soit créée à l'aide de l'énumération TaskCreationOptions, auquel cas les options de la tâche remplacent celles de la fabrique de tâches.

Tâches sans délégués

Dans certains cas, vous pouvez utiliser un Task pour encapsuler une opération asynchrone exécutée par un composant externe au lieu de votre propre délégué utilisateur. Si l’opération est basée sur le modèle de programmation asynchrone Begin/End, vous pouvez utiliser les méthodes FromAsync. Si ce n’est pas le cas, vous pouvez utiliser l’objet TaskCompletionSource<TResult> pour encapsuler l’opération dans une tâche et, de cette façon, bénéficier de certains des avantages de programmabilité Task, comme par exemple, la prise en charge de la propagation et des continuations d’exceptions. Pour plus d'informations, consultez TaskCompletionSource<TResult>.

Planificateurs personnalisés

La plupart des développeurs d'applications ou de bibliothèques ne se soucient pas du processeur sur lequel s'exécute la tâche, ni de la manière dont il synchronise son travail avec d'autres tâches ou de la façon dont il est planifié sur le System.Threading.ThreadPool. Ils demandent simplement à ce qu'il s'exécute aussi efficacement que possible sur l'ordinateur hôte. Si vous avez besoin d’un contrôle plus affiné sur les détails de la planification, la bibliothèque parallèle de tâches vous permet de configurer des paramètres dans le planificateur de tâches par défaut et vous permet même de fournir un planificateur personnalisé. Pour plus d'informations, consultez TaskScheduler.

La bibliothèque parallèle de tâches possède plusieurs nouveaux types publics qui sont utiles dans les scénarios parallèles et séquentiels. Ces derniers incluent plusieurs classes de collection thread-safe, rapides et évolutives dans l'espace de noms System.Collections.Concurrent, et plusieurs nouveaux types de synchronisation, tels que System.Threading.Semaphore et System.Threading.ManualResetEventSlim, qui sont plus efficaces que leurs prédécesseurs pour certains genres de charges de travail. D'autres types nouveaux dans .NET Framework 4, tels que System.Threading.Barrier et System.Threading.SpinLock, fournissent des fonctionnalités qui n'étaient pas disponibles dans les versions précédentes. Pour plus d’informations, consultez Structures de données pour la programmation parallèle.

Types de tâches personnalisés

Nous vous recommandons de ne pas hériter de System.Threading.Tasks.Task ou System.Threading.Tasks.Task<TResult>. Nous vous recommandons d'utiliser plutôt la propriété AsyncState pour associer d'autres données ou états à un objet Task ou Task<TResult>. Vous pouvez également utiliser des méthodes d'extension pour étendre les fonctionnalités des classes Task et Task<TResult>. Pour plus d’informations sur les méthodes d’extension, consultez Méthodes d’extension et Méthodes d’extension.

Si vous devez hériter de Task ou Task<TResult> , vous ne pouvez pas utiliser Run , ou les System.Threading.Tasks.TaskFactory System.Threading.Tasks.TaskFactory<TResult> classes, ou System.Threading.Tasks.TaskCompletionSource<TResult> pour créer des instances de votre type de tâche personnalisée, car ces mécanismes créent uniquement des Task Task<TResult> objets et. En outre, vous ne pouvez pas utiliser les mécanismes de continuation des tâches fournis par Task, Task<TResult>TaskFactory et TaskFactory<TResult> pour créer des instances de votre type de tâche personnalisé parce que ces mécanismes créent également uniquement des objets Task et Task<TResult>.

Intitulé Description
Chaînage des tâches à l’aide de tâches de continuation Décrit le fonctionnement des continuations.
Tâches enfants attachées et détachées Décrit la différence entre les tâches enfants attachées et les tâches enfants détachées.
Annulation de tâches Décrit la prise en charge de l'annulation intégrée dans l'objet Task.
Gestion des exceptions Décrit comment les exceptions sur les threads simultanés sont gérées.
Procédure : utiliser Parallel_Invoke pour exécuter des opérations parallèles Explique comment utiliser Invoke.
Procédure : retourner une valeur à partir d’une tâche Décrit comment retourner des valeurs à partir de tâches.
Procédure : annuler une tâche et ses enfants Décrit comment annuler des tâches.
Procédure : créer des tâches précalculées Décrit comment utiliser la méthode Task.FromResult pour récupérer les résultats d'opérations de téléchargement asynchrones qui sont conservées dans un cache.
Procédure : parcourir un arbre binaire avec des tâches parallèles Décrit comment utiliser des tâches pour parcourir un arbre binaire.
Procédure : désencapsuler une tâche imbriquée Montre également comment utiliser la méthode d'extension Unwrap.
Parallélisme des données Décrit comment utiliser For et ForEach pour créer des boucles parallèles sur des données.
Programmation parallèle Nœud de niveau supérieur pour la programmation parallèle .NET Framework.

Voir aussi