Planificateurs de tâches

Les planificateurs de tâches sont représentés par la classe System.Threading.Tasks.TaskScheduler. Un planificateur de tâches s'assure que le travail d'une tâche est finalement exécuté. Le planificateur de tâches par défaut est basé sur le ThreadPool du .NET Framework 4, qui fournit le vol de travail pour l'équilibrage de charge, l'injection/retrait du thread pour un débit maximal et une bonne performance globale. Ce doit être suffisant pour la plupart des scénarios. Toutefois, si vous avez besoin de fonctionnalités spéciales, vous pouvez créer un planificateur personnalisé et l'activer pour les requêtes ou les tâches spécifiques. Pour plus d'informations sur la création et l'utilisation d'un planificateur de tâches, consultez Comment : créer un planificateur de tâches limitant le degré d'accès concurrentiel. Pour obtenir d'autres exemples de planificateurs personnalisés, consultez Exemples d'extensions parallèles (page éventuellement en anglais) sur le site Web MSDN Code Gallery.

Planificateur de tâches par défaut et ThreadPool

Le planificateur par défaut pour la bibliothèque parallèle de tâches et PLINQ utilise le .NET Framework ThreadPool pour mettre le travail en file d'attente et l'exécuter. Dans le .NET Framework 4, le ThreadPool utilise les informations fournies par le type System.Threading.Tasks.Task pour prendre en charge efficacement le parallélisme affiné (unités éphémères de travail) souvent représenté par les requêtes et les tâches parallèles.

File d'attente globale de ThreadPool contrefiles d'attente locales

Comme dans les versions antérieures du .NET Framework, le ThreadPool maintient une file d'attente du travail de type premier entré, premier sorti (FIFO, first-in, first-out) globale pour les threads dans chaque domaine d'application. Chaque fois qu'un programme appelle QueueUserWorkItem (ou UnsafeQueueUserWorkItem), le travail est placé dans cette file d'attente partagée et finalement sorti de cette file d'attente vers le thread suivant qui devient disponible. Dans le .NET Framework 4, cette file d'attente a été améliorée pour utiliser un algorithme sans verrou qui ressemble à la classe ConcurrentQueue. En utilisant cette implémentation sans verrou, le ThreadPool passe moins de temps lorsqu'il met les éléments de travail en file d'attente et les sort de cette file d'attente. Cet avantage en matière de performances est disponible pour tous les programmes qui utilisent le ThreadPool.

Les tâches de niveau supérieur, qui sont des tâches qui ne sont pas créées dans le contexte d'une autre tâche sont mises en file d'attente globale tout comme un autre élément de travail. Toutefois, les tâches imbriquées ou enfants, créées dans le contexte d'une autre tâche sont gérées tout à fait différemment. Une tâche enfant ou imbriquée est mise en file d'attente locale qui est spécifique au thread sur lequel la tâche parente s'exécute. La tâche parente peut être une tâche de niveau supérieur ou également l'enfant d'une autre tâche. Lorsque ce thread est prêt pour exécuter davantage de travail, il regarde en premier dans la file d'attente locale. Si les éléments de travail attendent à cet endroit, il est possible d'y accéder rapidement. L'accès aux files d'attente locales est possible dans l'ordre du dernier entré, premier sorti (LIFO, last-in, first-out), afin de conserver la localité du cache et de réduire les conflits. Pour plus d'informations sur ces tâches enfants et imbriquées, consultez Tâches imbriquées et tâches enfants.

L'exemple suivant affiche quelques tâches planifiées sur la file d'attente globale et d'autres tâches planifiées sur la file d'attente locale.

Sub QueueTasks()

    ' TaskA is a top level task.
    Dim taskA = Task.Factory.StartNew(Sub()

                                          Console.WriteLine("I was enqueued on the thread pool's global queue.")

                                          ' TaskB is a nested task and TaskC is a child task. Both go to local queue.
                                          Dim taskB = New Task(Sub() Console.WriteLine("I was enqueued on the local queue."))
                                          Dim taskC = New Task(Sub() Console.WriteLine("I was enqueued on the local queue, too."),
                                                                  TaskCreationOptions.AttachedToParent)

                                          taskB.Start()
                                          taskC.Start()

                                      End Sub)
End Sub
void QueueTasks()
{
    // TaskA is a top level task.
    Task taskA = Task.Factory.StartNew( () =>
    {                
        Console.WriteLine("I was enqueued on the thread pool's global queue."); 

        // TaskB is a nested task and TaskC is a child task. Both go to local queue.
        Task taskB = new Task( ()=> Console.WriteLine("I was enqueued on the local queue."));
        Task taskC = new Task(() => Console.WriteLine("I was enqueued on the local queue, too."),
                                TaskCreationOptions.AttachedToParent);

        taskB.Start();
        taskC.Start();

    });
}

L'utilisation de files d'attente locales non seulement réduit la pression sur la file d'attente globale, mais permet aussi de tirer parti de la localité des données. Les éléments de travail dans la file d'attente locale référencent fréquemment des structures de données qui sont physiquement proches l'une de l'autre en mémoire. Dans ces cas, les données sont déjà dans le cache après que la première tâche a été exécutée et sont rapidement accessibles. Parallel LINQ (PLINQ) et la classe Parallel utilisent des tâches imbriquées et des tâches enfants et accomplissent des accélérations significatives à l'aide des files d'attente de travail locales.

Vol de travail

Le .NET Framework 4ThreadPool caractérise également un algorithme de vol de travail afin de contribuer à s'assurer qu'aucun thread ne se trouve inactif pendant que d'autres ont encore du travail dans leurs files d'attente. Lorsqu'un thread ThreadPool est prêt pour effectuer davantage de travail, il regarde en tête de sa file d'attente locale, puis dans la file d'attente globale, et enfin dans les files d'attente locales d'autres threads. S'il recherche un élément de travail dans la file d'attente locale d'un autre thread, il applique en premier l'heuristique afin de s'assurer qu'il peut exécuter le travail efficacement. Le cas échéant, il sort l'élément de travail de la queue de la file d'attente (dans l'ordre du premier entré, premier sorti). Cela réduit le conflit sur chaque file d'attente locale et permet de conserver la localité des données. Cette architecture contribue au travail d'équilibrage de charge .NET Framework 4 ThreadPool de manière plus efficace que les versions passées.

Tâches de longue durée

Vous pouvez explicitement empêcher une tâche d'être mise en file d'attente locale. Par exemple, vous savez peut-être qu'un élément de travail particulier fonctionnera pour une durée relativement longue et sera à même de bloquer tous les autres éléments de travail sur la file d'attente locale. Dans ce cas, vous pouvez spécifier l'option LongRunning, qui fournit un conseil au planificateur qu'un thread supplémentaire peut être obligatoire pour la tâche afin que d'autres threads ou éléments de travail ne soient pas bloqués par la progression sur la file d'attente locale. En utilisant cette option, vous évitez complètement le ThreadPool, notamment les files d'attente globale et locales.

Incorporation de tâche

Dans certains cas, lorsqu'une tâche est attendue, elle peut être exécutée de façon synchrone sur le thread qui exécute l'opération d'attente. Cela améliore la performance, comme cela élimine la nécessité d'un thread supplémentaire grâce à l'utilisation du thread existant qui, autrement, se serait bloqué. Pour empêcher des erreurs dues à la réentrance, l'incorporation de tâche se produit seulement lorsque la cible d'attente est recherchée dans la file d'attente locale du thread pertinent.

Spécification d'un contexte de synchronisation

Vous pouvez utiliser la méthode TaskScheduler.FromCurrentSynchronizationContext pour spécifier qu'une tâche doit être planifiée pour fonctionner sur un thread particulier. Cela s'avère utile dans les infrastructures telles que Windows Forms et Windows Presentation Foundation où l'accès aux objets d'interface utilisateur est souvent restreint au code qui s'exécute sur le même thread sur lequel l'objet d'interface utilisateur a été créé. Pour plus d'informations, consultez Comment : planifier le travail sur un contexte de synchronisation spécifié.

Voir aussi

Référence

TaskScheduler

Concepts

Bibliothèque parallèle de tâches

Comment : planifier le travail sur un contexte de synchronisation spécifié

Autres ressources

Comment : créer un planificateur de tâches limitant le degré d'accès concurrentiel

Fabriques de tâches