Partager via


Parallélisme des tâches (runtime d'accès concurrentiel)

Ce document décrit le rôle des tâches et des groupes de tâches pendant le runtime d'accès concurrentiel.Une tâche est une unité de travail qui exécute un travail spécifique.Une tâche en général exécute en parallèle avec d'autres tâches et peut être décomposée en outre, plus affiné, tâches.Un groupe de tâches organise une collection de tâches.

Utilisez les tâches lorsque vous écrivez du code asynchrone et souhaitez une opération se produire lorsque l'opération asynchrone se termine.Par exemple, vous pouvez utiliser une tâche de lire de façon asynchrone d'un fichier et d'une tâche de continuation, qui est illustrée plus loin dans ce document, de traiter les données après qu'il devienne disponible.Inversement, groupes de tâches d'utilisation pour décomposer un travail parallèle en plus petites parties.Par exemple, supposez que vous avez un algorithme récursif qui divise le travail restant en deux partitions.Vous pouvez utiliser des groupes de tâches pour exécuter ces partitions simultanément, puis attendez que le travail divisé se termine.

ConseilConseil

Lorsque vous souhaitez appliquer le même routine à chaque élément d'une collection en parallèle, utilisez un algorithme parallèle, tel que concurrency::parallel_for, au lieu d'une tâche ou d'un groupe de tâches.Pour plus d'informations sur les algorithmes parallèles, consultez Algorithmes parallèles.

Points clés

  • Lorsque vous passez des variables à une expression lambda par référence, vous devez garantir que la durée de vie de cette variable persiste jusqu'à ce que la tâche se termine.

  • Tâches de utilisation (la classe de concurrency::task ) lorsque vous écrivez du code asynchrone.

  • Utilisez les groupes de tâches (tels que la classe de concurrency::task_group ou l'algorithme de concurrency::parallel_invoke ) lorsque vous devez décomposer un travail parallèle en plus petites parties puis attendre que les plus petits se termine.

  • Utilisez la méthode de concurrency::task::then pour créer des suites.Une continuation est une tâche qui s'exécute de façon asynchrone après une autre tâche se termine.Vous pouvez connecter plusieurs suites pour former une chaîne de travail asynchrone.

  • Une suite tâche- base est toujours planifiée pour l'exécution lorsque l'antécédent est terminé, même lorsque l'antécédent est annulée ou lève une exception.

  • Utilisez concurrency::when_all pour créer une tâche qui se termine après chaque membre d'un ensemble de tâches se termine.Utilisez concurrency::when_any pour créer une tâche qui se termine une fois qu'un membre d'un ensemble de tâches se termine.

  • Les tâches et les groupes de tâches peuvent participer au mécanisme d'annulation de bibliothèque PPL.Pour plus d’informations, consultez Annulation dans la bibliothèque de modèles parallèles.

  • Pour savoir comment le runtime gère les exceptions levées par des tâches et des groupes de tâches, consultez l' Gestion des exceptions dans le runtime d'accès concurrentiel.

Dans ce document

  • Utilisation d'expressions lambda

  • La classe de tâche

  • Tâches de continuation

  • Valeur- Basé et des suites Tâche- Windows

  • Composer des tâches

    • La fonction de when_all

    • La fonction when_any

  • Exécution de la tâche différée

  • Groupes de tâches

  • Comparaison de task_group et de structured_task_group

  • Exemple

  • Programmation fiable

Utilisation d'expressions lambda

Les expressions lambda sont un moyen courant de définir le travail exécuté par les tâches et les groupes de tâches en raison de leur syntaxe succincte.Voici quelques conseils sur les utiliser :

  • Étant donné que les tâches s'exécutent généralement sur des threads d'arrière-plan, sachez de la durée de vie des objets où vous capturer des variables dans les expressions lambda.Lorsque vous capturer une variable par valeur, une copie de cette variable est effectuée dans le corps de l'expression lambda.Lorsque vous capturer par référence, une copie n'est pas effectuée.Par conséquent, vérifiez que la durée de vie d'une variable que vous capturer par référence survit à la tâche qui l'utilise.

  • En général ne capture pas les variables par qui sont alloués sur la pile.Cela signifie également que vous ne devez pas capturer les variables membres d'objets qui sont alloués sur la pile.

  • Soyez explicite sur les variables à capturer dans les expressions lambda pour vous aider à identifier ce que vous capturer par valeur et par référence.C'est pourquoi nous vous déconseillons d'utiliser les options d' [=] ou d' [&] pour les expressions lambda.

Un modèle commun est lorsqu'une tâche dans une chaîne de suite assigne à une variable, et une autre tâche lit cette variable.Vous ne pouvez pas capturer par valeur car chaque tâche de continuation maintiendrait une autre copie de cette variable.Pour les variables pile- allouées, vous ne pouvez pas non plus capturer par référence car la variable peut plus être valide.

Pour résoudre ce problème, utilisez un pointeur intelligent, tel que std::shared_ptr, pour encapsuler la variable et passez le pointeur intelligent par valeur.Ce faisant, l'objet sous-jacent peut être assigné à et lu à partir de et survivra aux tâches qui l'utilisent.Utilisez cette technique même si la variable est un pointeur ou un handle références compté (^) à un objet d'exécution windows.Voici un exemple de base :

// lambda-task-lifetime.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <string>

using namespace concurrency;
using namespace std;

task<wstring> write_to_string()
{
    // Create a shared pointer to a string that is 
    // assigned to and read by multiple tasks.
    // By using a shared pointer, the string outlives
    // the tasks, which can run in the background after
    // this function exits.
    auto s = make_shared<wstring>(L"Value 1");

    return create_task([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value.
        *s = L"Value 2";

    }).then([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value and return the string.
        *s = L"Value 3";
        return *s;
    });
}

int wmain()
{
    // Create a chain of tasks that work with a string.
    auto t = write_to_string();

    // Wait for the tasks to finish and print the result.
    wcout << L"Final value: " << t.get() << endl;
}

/* Output:
    Current value: Value 1
    Current value: Value 2
    Final value: Value 3
*/

Pour plus d'informations sur les expressions lambda, consultez Expressions lambda en C++.

[Supérieur]

La classe de tâche

Vous pouvez utiliser la classe de concurrency::task pour composer des tâches en jeu d'opérations dépendantes.Ce modèle de composition est pris en charge par la notion de suites.Une suite permet au code d'être exécuté lorsque le précédent, ou l'antécédent, tâche se termine.Le résultat de l'antécédent est passé comme entrée à une ou plusieurs tâches de continuation.Lorsque l'antécédent est terminé, toutes les tâches de continuation qui attendent sur sont planifiées pour l'exécution.Chaque tâche de continuation reçoit une copie du résultat de l'antécédent.Ensuite, ces tâches de continuation peuvent également être des tâches de l'antécédent pour d'autres suites, créant ainsi une chaîne des tâches.Les suites vous aident à créer des chaînes de longueur arbitraire les tâches qui ont des dépendances spécifiques entre elles.En outre, une tâche peut participer à l'annulation ou avant que les tâches démarre ou d'une manière coopérative pendant son exécution.Pour plus d'informations sur ce modèle d'annulation, consultez Annulation dans la bibliothèque de modèles parallèles.

task est une classe de modèle.Le paramètre de type T est le type du résultat qui est levée par la tâche.Ce type peut être void si la tâche ne retourne pas de valeur.T ne peut pas utiliser le modificateur d' const .

Lorsque vous créez une tâche, vous fournissez une fonction de travail qui exécute le corps de tâche.Cette fonction de travail est sous la forme d'une fonction lambda, de pointeur fonction, ou d'un objet de fonction.Pour attendre une tâche se termine sans obtenir le résultat, appelez la méthode de concurrency::task::wait .La méthode d' task::wait retourne une valeur de concurrency::task_status qui indique si la tâche terminée ou annulée.Pour obtenir le résultat de la tâche, appelez la méthode de concurrency::task::get .Cette méthode appelle task::wait pour attendre la tâche se termine, et par conséquent bloque l'exécution du thread actuel jusqu'à ce que le résultat soit disponible.

l'exemple suivant montre comment créer une tâche, attendre son résultat, et afficher sa valeur.Les exemples dans cette documentation utilise des fonctions lambda car ils fournissent une syntaxe plus succincte.Toutefois, vous pouvez également utiliser des pointeurs fonction et les objets de fonction lorsque vous utilisez des tâches.

// basic-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Create a task.
    task<int> t([]()
    {
        return 42;
    });

    // In this example, you don't necessarily need to call wait() because
    // the call to get() also waits for the result.
    t.wait();

    // Print the result.
    wcout << t.get() << endl;
}

/* Output:
    42
*/

La fonction de concurrency::create_task vous permet d'utiliser le mot clé d' auto au lieu de déclarer le type.Par exemple, prenons le code suivant qui crée et imprime la matrice d'identité :

// create-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <string>
#include <iostream>
#include <array>

using namespace concurrency;
using namespace std;

int wmain()
{
    task<array<array<int, 10>, 10>> create_identity_matrix([]
    {
        array<array<int, 10>, 10> matrix;
        int row = 0;
        for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
        {
            fill(begin(matrixRow), end(matrixRow), 0);
            matrixRow[row] = 1;
            row++;
        });
        return matrix;
    });

    auto print_matrix = create_identity_matrix.then([](array<array<int, 10>, 10> matrix)
    {
        for_each(begin(matrix), end(matrix), [](array<int, 10>& matrixRow) 
        {
            wstring comma;
            for_each(begin(matrixRow), end(matrixRow), [&comma](int n) 
            {
                wcout << comma << n;
                comma = L", ";
            });
            wcout << endl;
        });
    });

    print_matrix.wait();
}
/* Output:
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 1, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 1, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 1, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 1, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 1, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 1, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1
*/

Vous pouvez utiliser la fonction d' create_task pour créer l'opération équivalente.

auto create_identity_matrix = create_task([]
{
    array<array<int, 10>, 10> matrix;
    int row = 0;
    for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
    {
        fill(begin(matrixRow), end(matrixRow), 0);
        matrixRow[row] = 1;
        row++;
    });
    return matrix;
});

Si une exception est levée pendant l'exécution d'une tâche, marshale de runtime qui exception dans l'appel suivant à task::get ou à task::wait, ou une suite tâche- sur.Pour plus d'informations sur le mécanisme de gestion des exceptions de tâche, consultez l' Gestion des exceptions dans le runtime d'accès concurrentiel.

Pour obtenir un exemple qui utilise task, concurrency::task_completion_event, annulation, consultez Procédure pas à pas : connexion à l'aide de tâches et de la requête HTTP XML (IXHR2).(La classe d' task_completion_event est décrite plus loin dans ce document.)

ConseilConseil

Pour apprendre les détails spécifiques aux tâches dans les applications d' Windows Store , consultez Asynchronous programming in C++ et le Création d'opérations asynchrones en C++ pour les applications Windows Store.

[Supérieur]

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.Traditionnellement, cette opération s'effectue à l'aide de les méthodes de rappel.Dans le runtime d'accès concurrentiel, la même fonctionnalité est fournie 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é.À l'aide de les suites, vous pouvez :

  • Passez les données de l'antécédent à la continuation.

  • Spécifiez les conditions précises sous lesquelles la continuation est appelée ou pas appelée.

  • Annulez une suite ou avant qu'elle démarre ou de manière coopérative pendant son exécution.

  • Fournissez des conseils sur la manière dont la continuation doit être planifiée.(Cela s'applique à Windows Store des applications uniquement.(Pour plus d'informations, consultez Création d'opérations asynchrones en C++ pour les applications Windows Store).

  • Appelez plusieurs suites du même antécédent.

  • Appelez une continuation lorsque tous les ou un des plusieurs antécédents complets.

  • Chaînez des suites l'un après l'autre à n'importe quelle longueur.

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

Ces fonctions vous permettent d'exécuter une ou plusieurs tâches lorsque la première tâche se termine.Par exemple, vous pouvez créer une continuation qui compresse un fichier après que la première tâche le lire à partir de le disque.

L'exemple suivant modifie le précédent pour utiliser la méthode de concurrency::task::then pour planifier une continuation qui imprime la valeur de l'antécédent de tâche lorsqu'elle est disponible.

// basic-continuation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    {
        return 42;
    });

    t.then([](int result)
    {
        wcout << result << endl;
    }).wait();

    // Alternatively, you can chain the tasks directly and
    // eliminate the local variable.
    /*create_task([]() -> int
    {
        return 42;
    }).then([](int result)
    {
        wcout << result << endl;
    }).wait();*/
}

/* Output:
    42
*/

Vous pouvez enchaîner et imbriquer des tâches à n'importe quelle longueur.Une tâche peut également avoir plusieurs suites.L'exemple suivant montre une chaîne de base de continuation qui incrémente la valeur de la tâche précédente trois fois.

// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    { 
        return 0;
    });

    // Create a lambda that increments its input value.
    auto increment = [](int n) { return n + 1; };

    // Run a chain of continuations and print the result.
    int result = t.then(increment).then(increment).then(increment).get();
    wcout << result << endl;
}

/* Output:
    3
*/

Une continuation peut également retourner une autre tâche.S'il n'existe aucune annulation, cette tâche est exécutée avant la suite ultérieure.Cette technique est appelé le déroulement asynchrone.Le déroulement asynchrone est utile lorsque vous souhaitez exécuter le travail supplémentaire en arrière-plan, mais ne souhaite pas la tâche actuelle de bloquer le thread actuel.(C'est courant dans les applications d' Windows Store , où les suites peuvent s'exécuter sur le thread d'interface utilisateur).L'exemple suivant illustre trois tâches.La première tâche retourne une autre tâche exécutée avant une continuation.

// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]()
    {
        wcout << L"Task A" << endl;

        // Create an inner task that runs before any continuation
        // of the outer task.
        return create_task([]()
        {
            wcout << L"Task B" << endl;
        });
    });

    // Run and wait for a continuation of the outer task.
    t.then([]()
    {
        wcout << L"Task C" << endl;
    }).wait();
}

/* Output:
    Task A
    Task B
    Task C
*/

Important

Lorsqu'une suite d'une tâche retourne une tâche imbriquée de type N, la tâche résultant a le type N, pas task<N>, et se termine lorsque la tâche imbriquée terminée.En d'autres termes, la continuation exécute le déroulement de la tâche imbriquée.

[Supérieur]

Valeur- Basé et des suites Tâche- Windows

Étant donné un objet d' task dont le type de retour est T, vous pouvez fournir une valeur de type T ou task<T> à ses tâches de continuation.Une continuation qui prend le type T est appelé une suite valeur- sur.Une suite valeur- basée est planifiée pour l'exécution lorsque l'antécédent est terminé sans erreur et n'est pas annulée.Une continuation qui prend le type task<T> comme son paramètre est appelé une suite tâche- sur.Une suite tâche- base est toujours planifiée pour l'exécution lorsque l'antécédent est terminé, même lorsque l'antécédent est annulée ou lève une exception.Vous pouvez ensuite appeler task::get pour obtenir le résultat de l'antécédent.Si l'antécédent a été annulée, task::get lève concurrency::task_canceled.Si l'antécédent a levé une exception, le rethrows d' task::get qui exception.Une suite tâche- basée n'est pas marquée comme annulé lorsque son antécédent est annulé.

[Supérieur]

Composer des tâches

Cette section décrit concurrency::when_all et concurrency::when_any s'exécute, qui peut vous aider à composer de plusieurs tâches pour implémenter les modèles courants.

Dd492427.collapse_all(fr-fr,VS.110).gifLa fonction de when_all

La fonction d' when_all produit une tâche qui se termine après un ensemble de tâches terminées.Cette fonction retourne un objet de std::vector qui contient le résultat de chaque tâche dans le jeu.L'exemple de base suivant utilise when_all pour créer une tâche qui représente l'achèvement de trois autres tâches.

// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<void>, 3> tasks = 
    {
        create_task([] { wcout << L"Hello from taskA." << endl; }),
        create_task([] { wcout << L"Hello from taskB." << endl; }),
        create_task([] { wcout << L"Hello from taskC." << endl; })
    };

    auto joinTask = when_all(begin(tasks), end(tasks));

    // Print a message from the joining thread.
    wcout << L"Hello from the joining thread." << endl;

    // Wait for the tasks to finish.
    joinTask.wait();
}

/* Sample output:
    Hello from the joining thread.
    Hello from taskA.
    Hello from taskC.
    Hello from taskB.
*/

[!REMARQUE]

Les tâches que vous passez à when_all doivent être uniformes.En d'autres termes, ils doivent retourner le même type.

Vous pouvez également utiliser la syntaxe d' && pour produire une tâche qui se termine après qu'un ensemble de tâches terminées, comme indiqué dans l'exemple suivant.

auto t = t1 && t2; // same as when_all

Il est courant d'utiliser une continuation avec when_all pour exécuter l'action après qu'un ensemble de tâches se terminent.L'exemple suivant modifie le précédent pour imprimer la somme de trois tâches que chaque produit un résultat de int .

// Start multiple tasks.
array<task<int>, 3> tasks =
{
    create_task([]() -> int { return 88; }),
    create_task([]() -> int { return 42; }),
    create_task([]() -> int { return 99; })
};

auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{
    wcout << L"The sum is " 
          << accumulate(begin(results), end(results), 0)
          << L'.' << endl;
});

// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;

// Wait for the tasks to finish.
joinTask.wait();

/* Output:
    Hello from the joining thread.
    The sum is 229.
*/

Dans cet exemple, vous pouvez également spécifier task<vector<int>> pour produire une suite tâche- sur.

Mise en gardeAttention

Si une tâche dans un ensemble de tâches est annulé ou lève une exception, when_all immédiatement se termine et n'attend pas les tâches restantes de temps.Si une exception est levée, les rethrows d'exécution exception lorsque vous appelez task::get ou task::wait sur l'objet d' task qu' when_all retourne.Si plusieurs tâche lève, le runtime sélectionne l'un d'eux.Par conséquent, s'il lève une exception, assurez -vous que vous attendez que toutes les tâches soient terminées.

[Supérieur]

Dd492427.collapse_all(fr-fr,VS.110).gifLa fonction when_any

La fonction d' when_any produit une tâche qui se termine lorsque la première tâche dans un ensemble de tâches se termine.Cette fonction retourne un objet de std::pair qui contient le résultat de la tâche achevée et de l'index de cette tâche dans le jeu.

La fonction d' when_any est particulièrement utile dans les cas suivants :

  • Opérations redondantes.Considérez un algorithme ou une opération qui peuvent être exécutés plusieurs façons.Vous pouvez utiliser la fonction d' when_any pour sélectionner l'exécution de la fin d'abord et annulent les opérations restantes.

  • Opérations entrelacées.Vous pouvez démarrer plusieurs opérations que tout doit se terminer et utiliser la fonction d' when_any pour traiter les résultats à mesure que chaque opération se termine.Une fois qu'une opération terminée, vous pouvez démarrer une ou plusieurs tâches supplémentaires.

  • Opérations étranglées.Vous pouvez utiliser la fonction d' when_any pour étendre l'exemple précédent en limitant le nombre d'opérations simultanées.

  • Opérations expirées.Vous pouvez utiliser la fonction d' when_any pour sélectionner entre une ou plusieurs tâches et une tâche qui termine après une heure spécifique.

Comme avec when_all, il est courant d'utiliser une continuation qui a when_any pour exécuter l'action lorsque les premiers dans un ensemble de tâches se terminent.L'exemple de base suivant utilise when_any pour créer une tâche qui se termine lorsque le premier de trois autres tâches se termine.

// select-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<int>, 3> tasks = {
        create_task([]() -> int { return 88; }),
        create_task([]() -> int { return 42; }),
        create_task([]() -> int { return 99; })
    };

    // Select the first to finish.
    when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
    {
        wcout << "First task to finish returns "
              << result.first
              << L" and has index "
              << result.second
              << L'.' << endl;
    }).wait();
}

/* Sample output:
    First task to finish returns 42 and has index 1.
*/

Dans cet exemple, vous pouvez également spécifier task<pair<int, size_t>> pour produire une suite tâche- sur.

[!REMARQUE]

Comme avec when_all, les tâches que vous passez à when_any doivent retourner le même type.

Vous pouvez également utiliser la syntaxe d' || pour produire une tâche qui se termine après que la première tâche dans un ensemble de tâches se termine, comme indiqué dans l'exemple suivant.

auto t = t1 || t2; // same as when_any

[Supérieur]

Exécution de la tâche différée

Il est parfois nécessaire de différer l'exécution d'une tâche jusqu'à ce qu'une condition est satisfaite, ou de commencer une tâche en réponse à un événement externe.Par exemple, dans la programmation asynchrone, vous devrez peut-être démarrer une tâche en réponse à un événement de terminaison d'E/S.

Deux façons d'accomplir cette tâche est d'utiliser une continuation ou de commencer une tâche et une attente sur un événement à l'intérieur de la fonction de travail de la tâche.Toutefois, dans certains cas où il est impossible d'utiliser une de ces techniques.Par exemple, pour créer une suite, vous devez avoir l'antécédent.Toutefois, si vous n'avez pas l'antécédent, vous pouvez créer un événement d'achèvement de tâche et une chaîne ultérieure cet événement d'achèvement à l'antécédent de tâche lorsqu'il devient disponible.En outre, étant donné qu'une tâche en attente bloque également un thread, vous pouvez utiliser des événements d'achèvement des tâches pour exécuter un travail lorsqu'une opération asynchrone se termine, puis relâchez par conséquent un thread.

Les aide de la classe de concurrency::task_completion_event simplifient une telle composition des tâches.Comme la classe d' task , le paramètre de type T est le type du résultat qui est levée par la tâche.Ce type peut être void si la tâche ne retourne pas de valeur.T ne peut pas utiliser le modificateur d' const .En général, un objet d' task_completion_event est fourni à un thread ou une tâche qui le signaleront lorsque la valeur de il devient disponible.En même temps, un ou plusieurs tâches sont définies comme écouteurs de cet événement.Lorsque l'événement est défini, l'écouteur charge terminé et leurs suites sont planifiées pour fonctionner.

Pour obtenir un exemple qui utilise task_completion_event pour implémenter une tâche qui se termine après un délai, consultez Comment : créer une tâche qui se termine après un certain délai..

[Supérieur]

Groupes de tâches

Un groupe de tâches organise une collection de tâches.Les groupes de tâches poussent les tâches vers une file d'attente de vol de travail.Le planificateur supprime les tâches de cette file d'attente et les exécute sur les ressources de calcul disponibles.Après avoir ajouté des tâches à un groupe de tâches, vous pouvez attendre que toutes les tâches soient terminées ou annuler les tâches qui n'ont pas encore commencé.

La bibliothèque PPL utilise les classes de concurrency::task_group et de concurrency::structured_task_group pour représenter des groupes de tâches, et la classe de concurrency::task_handle pour représenter les tâches qui s'exécutent à ces groupes.La classe task_handle encapsule le code qui exécute un travail.Comme la classe d' task , la fonction de travail est sous la forme d'une fonction lambda, de pointeur fonction, ou d'un objet de fonction.En général, vous n'avez pas besoin d'utiliser des objets task_handle directement.Au lieu de cela, vous passez des fonctions de travail à un groupe de tâches et le groupe de tâches crée et gère les objets task_handle.

La bibliothèque PPL divise les groupes de tâches en deux catégories : les groupes de tâches non structurés et les groupes de tâches structurés.La bibliothèque PPL utilise la classe d' task_group pour représenter des groupes de tâches non structurés et la classe d' structured_task_group pour représenter des groupes de tâches structurés.

Important

La bibliothèque PPL définit également l'algorithme de concurrency::parallel_invoke , qui utilise la classe d' structured_task_group pour exécuter un ensemble de tâches en parallèle.Étant donné que l'algorithme parallel_invoke a une syntaxe plus succincte, nous vous conseillons de l'utiliser au lieu d'utiliser la classe structured_task_group, lorsque vous le pouvez.La rubrique Algorithmes parallèles décrit parallel_invoke plus en détail.

Utilisez parallel_invoke lorsque vous voulez exécuter simultanément plusieurs tâches indépendantes et que vous devez attendre que toutes les tâches soient terminées avant de continuer.Cette technique est souvent désigné par le parallélisme de bifurcation/jointure .Utilisez task_group lorsque vous voulez exécuter simultanément plusieurs tâches indépendantes, mais que vous souhaitez attendre que les tâches se finissent ultérieurement.Par exemple, vous pouvez ajouter des tâches à un objet task_group et attendre qu'elles se terminent dans une autre fonction ou dans un autre thread.

Les groupes de tâches prennent en charge le concept d'annulation.L'annulation vous permet de signaler à toutes les tâches actives que vous souhaitez annuler l'opération globale.L'annulation empêche également le lancement des tâches qui n'ont pas encore commencé.Pour plus d'informations sur l'annulation, consultez Annulation dans la bibliothèque de modèles parallèles.

Le runtime fournit également un modèle de gestion des exceptions qui vous permet de lever une exception à partir d'une tâche et de gérer cette exception tout en attendant que le groupe de tâches associé se termine.Pour plus d'informations sur ce modèle de gestion des exceptions, consultez Gestion des exceptions dans le runtime d'accès concurrentiel.

[Supérieur]

Comparaison de task_group et de structured_task_group

Bien que nous recommandions que vous utilisez task_group ou parallel_invoke au lieu de la classe d' structured_task_group , dans certains cas où vous souhaitez utiliser structured_task_group, par exemple, lorsque vous écrivez un algorithme parallèle qui exécute un nombre variable de tâches ou requiert la prise en charge de l'annulation.Cette section explique les différences entre les classes task_group et structured_task_group.

La classe task_group est thread-safe.Par conséquent vous pouvez ajouter des tâches à un objet d' task_group des threads et attendre ou annuler un objet d' task_group plusieurs threads.La construction et la destruction d'un objet structured_task_group doit se produire dans la même portée lexicale.De plus, toutes les opérations sur un objet structured_task_group doivent se produire sur le même thread.L'exception à cette règle sont les méthodes de concurrency::structured_task_group::cancel et de concurrency::structured_task_group::is_canceling .Une tâche enfant peut appeler ces méthodes pour annuler le groupe de tâches parent ou vérifier l'annulation à tout moment.

Vous pouvez exécuter des tâches supplémentaires sur un objet d' task_group après avoir appelé la méthode de concurrency::task_group::wait ou de concurrency::task_group::run_and_wait .Inversement, vous ne pouvez pas exécuter des tâches supplémentaires sur un objet d' structured_task_group une fois que vous appelez les méthodes de concurrency::structured_task_group::wait ou de concurrency::structured_task_group::run_and_wait .

Étant donné que la classe structured_task_group ne synchronise pas d'un thread à un autre, sa charge d'exécution est inférieure à celle de la classe task_group.Par conséquent, si votre problème ne nécessite pas la planification du travail à partir de plusieurs threads et que vous ne pouvez pas utiliser l'algorithme parallel_invoke, la classe structured_task_group, peut vous aider à écrire du code plus performant.

Si vous utilisez un objet structured_task_group à l'intérieur d'un autre objet structured_task_group, l'objet interne doit se terminer et être détruit avant que l'objet externe se termine.La classe task_group ne nécessite pas que les groupes de tâches imbriqués se terminent avant les groupes externes.

Les groupes de tâches non structurés et les groupes de tâches structurés utilisent les handles de tâches de différentes façons.Vous pouvez passer directement des fonctions de travail à un objet task_group. L'objet task_group pourra alors créer et gérer le handle de tâches pour vous.La classe structured_task_group nécessite la gestion d'un objet task_handle pour chaque tâche.Chaque objet task_handle doit rester valide pendant toute la durée de vie de l'objet structured_task_group associé.Utilisez la fonction de concurrency::make_task pour créer un objet d' task_handle , comme indiqué dans l'exemple de base suivant :

// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>

using namespace concurrency;

int wmain()
{
   // Use the make_task function to define several tasks.
   auto task1 = make_task([] { /*TODO: Define the task body.*/ });
   auto task2 = make_task([] { /*TODO: Define the task body.*/ });
   auto task3 = make_task([] { /*TODO: Define the task body.*/ });

   // Create a structured task group and run the tasks concurrently.

   structured_task_group tasks;

   tasks.run(task1);
   tasks.run(task2);
   tasks.run_and_wait(task3);
}

Pour gérer les handles de tâche lorsque le nombre de tâches est variable, utilisez une routine d'allocation de tâches, comme _malloca ou une classe de conteneur, comme std::vector.

task_group et structured_task_group prennent tous les deux en charge l'annulation.Pour plus d'informations sur l'annulation, consultez Annulation dans la bibliothèque de modèles parallèles.

[Supérieur]

Exemple

L'exemple de base suivant montre comment utiliser des groupes de tâches.Cet exemple utilise l'algorithme parallel_invoke pour effectuer deux tâches simultanément.Chaque tâche ajoute une sous-tâche à un objet task_group.Notez que la classe task_group permet à plusieurs tâches d'y ajouter des tâches simultanément.

// using-task-groups.cpp
// compile with: /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>

using namespace concurrency;
using namespace std;

// Prints a message to the console.
template<typename T>
void print_message(T t)
{
   wstringstream ss;
   ss << L"Message from task: " << t << endl;
   wcout << ss.str(); 
}

int wmain()
{  
   // A task_group object that can be used from multiple threads.
   task_group tasks;

   // Concurrently add several tasks to the task_group object.
   parallel_invoke(
      [&] {
         // Add a few tasks to the task_group object.
         tasks.run([] { print_message(L"Hello"); });
         tasks.run([] { print_message(42); });
      },
      [&] {
         // Add one additional task to the task_group object.
         tasks.run([] { print_message(3.14); });
      }
   );

   // Wait for all tasks to finish.
   tasks.wait();
}

Voici un exemple de sortie pour cet exemple :

Message from task: Hello
Message from task: 3.14
Message from task: 42

Étant donné que l'algorithme parallel_invoke effectue des tâches simultanément, l'ordre des messages de sortie peut varier.

Pour obtenir des exemples complets utilisant l'algorithme parallel_invoke, consultez Comment : utiliser parallel_invoke pour écrire une routine de tri parallèle et Comment : utiliser parallel_invoke pour exécuter des opérations parallèles.Pour obtenir un exemple complet utilisant la classe task_group pour implémenter des tâches asynchrones futures, consultez Procédure pas à pas : implémentation de tâches futures.

[Supérieur]

Programmation fiable

Assurez -vous que vous comprenez le rôle de l'annulation et la gestion des exceptions lorsque vous utilisez des tâches, des groupes de tâches, et des algorithmes parallèles.Par exemple, dans une arborescence de travail parallèle, une tâche qui est annulée empêche l'exécution des tâches enfants.Cela peut entraîner des problèmes si l'une des tâches enfants effectue une opération importante pour votre application, telle que la libération d'une ressource.En outre, si une tâche enfant lève une exception, cette exception peut se propager à travers un destructeur d'objet et provoquer un comportement non défini dans votre application.Pour obtenir un exemple illustrant ces éléments, consultez la section Understand how Cancellation and Exception Handling Affect Object Destruction des meilleures pratiques du document relatif à la bibliothèque de modèles parallèles (PPL).Pour plus d'informations sur les modèles d'annulation et de gestion des exceptions dans la bibliothèque PPL, consultez Annulation dans la bibliothèque de modèles parallèles et Gestion des exceptions dans le runtime d'accès concurrentiel.

[Supérieur]

Rubriques connexes

Titre

Description

Comment : utiliser parallel_invoke pour écrire une routine de tri parallèle

Indique comment utiliser l'algorithme parallel_invoke pour améliorer les performances de l'algorithme de tri bitonique.

Comment : utiliser parallel_invoke pour exécuter des opérations parallèles

Indique comment utiliser l'algorithme parallel_invoke pour améliorer les performances d'un programme qui effectue plusieurs opérations sur une source de données partagée.

Comment : créer une tâche qui se termine après un certain délai.

Indique comment utiliser task, cancellation_token_source, cancellation_token, les classes et d' task_completion_event pour créer une tâche qui se termine après un délai.

Procédure pas à pas : implémentation de tâches futures

Indique comment combiner les fonctionnalités existantes du runtime d'accès concurrentiel afin d'en étendre et optimiser l'utilisation.

Bibliothèque de modèles parallèles

Décrit la bibliothèque PPL, qui fournit un modèle de programmation impérative pour le développement d'applications simultanées.

Référence

task (Concurrency Runtime), classe

task_completion_event, classe

when_all, fonction

when_any, fonction

task_group, classe

parallel_invoke, fonction

structured_task_group, classe