Annulation dans la bibliothèque de modèles parallèles

Ce document explique le rôle de l'annulation dans la bibliothèque de modèles parallèles (PPL), comment annuler un travail parallèle, et comment déterminer quand le travail parallèle est annulé.

[!REMARQUE]

Le runtime utilise la gestion des exceptions pour implémenter l'annulation.Par conséquent, vous ne devez ni intercepter, ni gérer ces exceptions dans votre code.De plus, nous vous recommandons d'écrire du code sécurisé du point de vue des exceptions dans les corps de fonction de vos tâches.Par exemple, vous pouvez utiliser le modèle RAII (Resource Acquisition Is Initialization) pour vérifier que les ressources sont gérées correctement lorsqu'une exception est levée dans le corps d'une tâche.Pour obtenir un exemple complet utilisant le modèle RAII pour nettoyer une ressource dans une tâche annulable, consultez Procédure pas à pas : suppression de travail d'un thread d'interface utilisateur.

Points clés

  • l'annulation est coopérative et implique la coordination entre le code qui demande l'annulation et la tâche qui répond à l'annulation.

  • Si possible, jetons d'annulation d'utilisation pour annuler le travail.La classe de concurrency::cancellation_token définit un jeton d'annulation.

  • Lorsque vous utilisez des jetons d'annulation, utilisez la méthode de concurrency::cancellation_token_source::cancel pour initialiser l'annulation et concurrency::is_task_cancellation_requested et concurrency::cancel_current_task fonctionne pour répondre à l'annulation.

  • l'annulation ne se produit pas immédiatement.Bien que de travail ne soient pas démarrés si une tâche ou un groupe de tâches est annulé, le travail actif doit vérifier et répondre à l'annulation.

  • Une suite valeur- basée hérite jeton d'annulation de son antécédent.Une suite tâche- basée n'hérite jamais le jeton de son antécédent.

  • Utilisez la méthode de concurrency::cancellation_token::none lorsque vous appelez un constructeur ou fonctionnent qui prend un objet d' cancellation_token mais vous ne souhaitez pas l'exécution pour être annulable.En outre, si vous ne passez pas un jeton d'annulation au constructeur de concurrency::task ou à la fonction de concurrency::create_task , cette tâche n'est pas annulable.

Dans ce document

  • Arborescences de travail parallèle

  • Annulation de tâches parallèles

    • À l'aide d'un jeton d'annulation pour annuler du travail parallèle

    • Utilisation de la méthode cancel pour annuler du travail parallèle

    • Utilisation d'exceptions pour annuler du travail parallèle

  • Annulation d'algorithmes parallèles

  • Quand ne pas utiliser l'annulation

Arborescences de travail parallèle

La bibliothèque PPL utilise des tâches et des groupes de tâches pour gérer les tâches affinées et les calculs.Vous pouvez imbriquer des groupes de tâches pour former des arborescences de travail parallèle.L'illustration suivante montre une arborescence de travail parallèle.Dans cette illustration, tg1 et tg2 représentent des groupes de tâches ; t1, t2, t3, t4, et t5 représentent le travail que les groupes de tâches effectuent.

Arborescence de travail parallèle

L'exemple suivant montre le code qui est obligatoire pour créer l'arborescence dans l'illustration.Dans cet exemple, tg1 et tg2 sont des objets de concurrency::structured_task_group ; t1, t2, t3, t4, et t5 sont des objets de concurrency::task_handle .

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

using namespace concurrency;
using namespace std;

void create_task_tree()
{   
   // Create a task group that serves as the root of the tree.
   structured_task_group tg1;

   // Create a task that contains a nested task group.
   auto t1 = make_task([&] {
      structured_task_group tg2;

      // Create a child task.
      auto t4 = make_task([&] {
         // TODO: Perform work here.
      });

      // Create a child task.
      auto t5 = make_task([&] {
         // TODO: Perform work here.
      });

      // Run the child tasks and wait for them to finish.
      tg2.run(t4);
      tg2.run(t5);
      tg2.wait();
   });

   // Create a child task.
   auto t2 = make_task([&] {
      // TODO: Perform work here.
   });

   // Create a child task.
   auto t3 = make_task([&] {
      // TODO: Perform work here.
   });

   // Run the child tasks and wait for them to finish.
   tg1.run(t1);
   tg1.run(t2);
   tg1.run(t3);
   tg1.wait();   
}

Vous pouvez également utiliser la classe de concurrency::task_group pour créer une arborescence similaire de travail.La classe de concurrency::task prend également en charge la notion d'une arborescence de travail.Toutefois, une arborescence d' task est une arborescence des dépendances.Dans une arborescence d' task , les travaux futurs se terminent après travail actif.Dans une arborescence de groupe de tâches, le travail interne se termine avant travail externe.Pour plus d'informations sur les différences entre les tâches et les groupes de tâches, consultez Parallélisme des tâches (runtime d'accès concurrentiel).

[Supérieur]

Annulation de tâches parallèles

Il existe plusieurs façons d'annuler du travail parallèle.La meilleure méthode consiste à utiliser un jeton d'annulation.Les groupes de tâches prennent également en charge la méthode de concurrency::task_group::cancel et la méthode de concurrency::structured_task_group::cancel .La méthode finale est de lever une exception dans le corps d'une fonction de travail tâche.Quelle que soit la méthode vous choisissez, notamment que l'annulation ne se produit pas immédiatement.Bien que de travail ne soient pas démarrés si une tâche ou un groupe de tâches est annulé, le travail actif doit vérifier et répondre à l'annulation.

Pour obtenir plus d'exemples que les tâches parallèles d'annulation, consultez Procédure pas à pas : connexion à l'aide de tâches et de la requête HTTP XML (IXHR2), Comment : utiliser l'annulation pour rompre une boucle parallèle, et Comment : utiliser la gestion des exceptions pour rompre une boucle parallèle.

Dd984117.collapse_all(fr-fr,VS.110).gifÀ l'aide d'un jeton d'annulation pour annuler du travail parallèle

task, task_group, et les classes d' structured_task_group prennent en charge l'annulation via l'utilisation de jetons d'annulation.La bibliothèque PPL définit les classes de concurrency::cancellation_token_source et de concurrency::cancellation_token à cet effet.Lorsque vous utilisez un jeton d'annulation pour annuler du travail, le runtime ne démarre pas de travail qui s'abonnent à ce jeton.Fonctionnent déjà active peut contrôler son jeton d'annulation et arrêter lorsqu'elle peut.

Pour initialiser l'annulation, appelez la méthode de concurrency::cancellation_token_source::cancel .Vous répondez à l'annulation de plusieurs manières :

  • Pour les objets d' task , utilisez les fonctions de concurrency::is_task_cancellation_requested et de concurrency::cancel_current_task .cancel_current_task annule la tâche actuelle et chacune de ses continuations valeur- sur.(Il n'annule pas le jeton d'annulation qui est associée à la tâche ou ses continuations.)

  • Pour les groupes de tâches et des algorithmes parallèles, utilisez la fonction de concurrency::is_current_task_group_canceling pour détecter l'annulation et retourner rapidement possible du corps de tâche lorsque cette fonction retourne true.(N'appelez pas cancel_current_task d'un groupe de tâches.)

l'exemple suivant montre le premier modèle de base pour l'annulation de tâche.Le corps de tâche vérifie parfois l'annulation dans une boucle.

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

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (is_task_cancellation_requested())
            {
                // TODO: Perform any necessary cleanup here...

                // Cancel the current task.
                cancel_current_task();
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;
    t.wait();

    wcout << L"Done." << endl;
}

/* Sample output:
    Creating task...
    Performing work...
    Performing work...
    Performing work...
    Performing work...
    Canceling task...
    Waiting for task to complete...
    Done.
*/

Lève de fonction d' cancel_current_task ; par conséquent, vous n'avez pas besoin de revenir explicitement de la boucle de actuelle ou de la fonction.

ConseilConseil

Sinon, vous pouvez appeler la fonction de concurrency::interruption_point au lieu d' is_task_cancellation_requested et d' cancel_current_task.

Il est important d'appeler cancel_current_task lorsque vous répondez à l'annulation car il transite la tâche à l'état canceled.Si vous retournez haut au lieu d'appeler cancel_current_task, les transitions d'exécution à l'état terminé et toutes les suites valeur- applications sont exécutées.

Mise en gardeAttention

Ne levez jamais task_canceled de votre code.Appel cancel_current_task à la place.

Lorsque les conseils de tâche dans l'état annulé, la méthode de concurrency::task::get lève concurrency::task_canceled.(En revanche, concurrency::task::wait retourne task_status::canceled et ne lève pas.) L'exemple suivant illustre ce comportement pour une suite tâche- sur.Une suite tâche- base est toujours appelée, même lorsque l'antécédent est annulé.

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

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t1 = create_task([]() -> int
    {
        // Cancel the task.
        cancel_current_task();
    });

    // Create a continuation that retrieves the value from the previous.
    auto t2 = t1.then([](task<int> t)
    {
        try
        {
            int n = t.get();
            wcout << L"The previous task returned " << n << L'.' << endl;
        }
        catch (const task_canceled& e)
        {
            wcout << L"The previous task was canceled." << endl;
        }
    });

    // Wait for all tasks to complete.
    t2.wait();
}
/* Output:
    The previous task was canceled.
*/

Étant donné que les suites valeur- applications héritent la marque de son antécédent à moins qu'elles ont été créées avec un jeton explicite, les suites entrez immédiatement l'état canceled même lorsque l'antécédent exécution.Par conséquent, toute exception qui est levée par l'antécédent de tâche après que l'annulation ne soit pas propagée aux tâches de continuation.L'annulation remplace toujours l'état de l'antécédent.L'exemple suivant ressemble au précédent, mais présente le comportement d'une suite valeur- sur.

auto t1 = create_task([]() -> int
{
    // Cancel the task.
    cancel_current_task();
});

// Create a continuation that retrieves the value from the previous.
auto t2 = t1.then([](int n)
{
    wcout << L"The previous task returned " << n << L'.' << endl;
});

try
{
    // Wait for all tasks to complete.
    t2.get();
}
catch (const task_canceled& e)
{
    wcout << L"The task was canceled." << endl;
}
/* Output:
    The task was canceled.
*/
Mise en gardeAttention

Si vous ne passez pas un jeton d'annulation au constructeur d' task ou à la fonction de concurrency::create_task , cette tâche n'est pas annulable.De plus, vous devez passer le même jeton d'annulation au constructeur de toutes les tâches imbriquées (autrement dit, les tâches créées dans le corps d'une autre tâche) pour annuler toutes les tâches simultanément.

Vous pouvez exécuter du code arbitraire lorsqu'un jeton d'annulation est annulée.Par exemple, si l'utilisateur choisit un bouton Annuler sur l'interface utilisateur à annuler l'exécution, vous pouvez désactiver ce bouton jusqu'à ce que l'utilisateur démarre une autre opération.L'exemple suivant montre comment utiliser la méthode de concurrency::cancellation_token::register_callback pour stocker une fonction de rappel qui fonctionne lorsqu'un jeton d'annulation est annulée.

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

using namespace concurrency;
using namespace std;

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token();

    // An event that is set in the cancellation callback.
    event e;

    cancellation_token_registration cookie;
    cookie = token.register_callback([&e, token, &cookie]()
    {
        wcout << L"In cancellation callback..." << endl;
        e.set();

        // Although not required, demonstrate how to unregister 
        // the callback.
        token.deregister_callback(cookie);
    });

    wcout << L"Creating task..." << endl;

    // Create a task that waits to be canceled.
    auto t = create_task([&e]
    {
        e.wait();
    }, token);

    // Cancel the task.
    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    t.wait();

    wcout << L"Done." << endl;
}
/* Sample output:
    Creating task...
    Canceling task...
    In cancellation callback...
    Done.
*/

Le document Parallélisme des tâches (runtime d'accès concurrentiel) montre la différence entre les suites valeur- sur et tâche- sur.Si vous ne fournissez pas l'objet d' cancellation_token à une tâche de continuation, la continuation hérite jeton d'annulation de l'antécédent des façons suivantes :

  • Une suite valeur- basée hérite toujours jeton d'annulation de l'antécédent.

  • Une suite tâche- basée n'hérite jamais jeton d'annulation de l'antécédent.La seule façon de rendre une suite tâche- basée annulable est de passer explicitement un jeton d'annulation.

Ces comportements ne sont pas affectés par une tâche censurée (autrement dit, une qui lève une exception).Dans ce cas, une suite valeur-basée est annulée ; une suite tâche-basée n'est pas annulée.

Mise en gardeAttention

Une tâche créée dans une autre tâche (en d'autres termes, une tâche imbriquée) n'hérite pas le jeton d'annulation de la tâche parente.Une seule suite valeur- basée hérite jeton d'annulation de son antécédent.

ConseilConseil

Utilisez la méthode de concurrency::cancellation_token::none lorsque vous appelez un constructeur ou fonctionnent qui prend un objet d' cancellation_token et vous ne souhaitez pas l'exécution pour être annulable.

Vous pouvez également fournir un jeton d'annulation au constructeur d'un objet d' task_group ou d' structured_task_group .Un aspect important de cela est que les groupes de tâches enfants héritent ce jeton d'annulation.Pour obtenir un exemple qui illustre ce concept à l'aide de la fonction de concurrency::run_with_cancellation_token à exécuter pour appeler parallel_for, consultez Annulation des algorithmes parallèles plus loin dans ce document.

[Supérieur]

Dd984117.collapse_all(fr-fr,VS.110).gifJetons d'annulation et la composition de tâche

Les fonctions de concurrency::when_all et de concurrency::when_any peuvent vous aider à composer de plusieurs tâches pour implémenter les modèles courants.Cette section décrit comment ces fonctions utilisent des jetons d'annulation.

Lorsque vous fournissez un jeton d'annulation à l'un ou l'autre la fonction d' when_all et d' when_any , annule les de cette fonction uniquement lorsque ce jeton d'annulation est annulée ou lorsque l'une des extrémités de tâches des parties prenantes dans un état annulé ou lève une exception.

La fonction d' when_all hérite jeton d'annulation de chaque tâche qui compose l'opération globale lorsque vous ne fournissez pas de jeton d'annulation à celui-ci.La tâche retournée d' when_all est annulée lorsque l'un de ces jetons sont annulées et au moins une des parties prenantes que les tâches n'a pas encore démarré ou exécute.Un comportement similaire se produit lorsque l'une des tâches lève une exception – la tâche retournée d' when_all est immédiatement annulée avec cette exception.

Le runtime sélectionne le jeton d'annulation de la tâche retournée à partir de la fonction d' when_any lorsque cette tâche se termine.Si aucune des tâches de participant ne se termine par un état terminé et un ou plusieurs des tâches lève une exception, l'une des tâches qui se sont levées est choisie pour terminer when_any et son jeton est sélectionné comme jeton de la tâche finale.Si plusieurs la tâche se termine dans l'état terminé, la tâche qui a retourné des conseils de travail d' when_any dans un état terminé.Les tests de runtime pour choisir une tâche achevée dont le jeton n'est pas annulée au moment de l'achèvement de sorte que la tâche retournée d' when_any n'est pas immédiatement annulés bien que d'autres tâches exécutantes peuvent s'exécuter ultérieurement.

[Supérieur]

Dd984117.collapse_all(fr-fr,VS.110).gifUtilisation de la méthode cancel pour annuler du travail parallèle

Les méthodes de concurrency::task_group::cancel et de concurrency::structured_task_group::cancel définissent un groupe de tâches à l'état canceled.Une fois que vous avez appelé cancel, le groupe de tâches ne démarre pas les tâches ultérieures.Les méthodes cancel peuvent être appelées par plusieurs tâches enfants.Une tâche annulée fait retourner les méthodes de concurrency::task_group::wait et de concurrency::structured_task_group::waitconcurrency::canceled.

Si un groupe de tâches est annulé, les appels de chaque tâche enfant dans le runtime peuvent déclencher un point d'interruption, ce qui fait en sorte que le runtime lève et intercepte un type d'exception interne pour annuler les tâches actives.Le runtime d'accès concurrentiel ne définit pas de points d'interruption spécifiques ; ils peuvent se produire dans n'importe quel appel au runtime.Le runtime doit gérer les exceptions qu'il lève afin d'effectuer l'annulation.Par conséquent, vous ne devez pas gérer d'exceptions inconnues dans le corps d'une tâche.

Si une tâche enfant exécute une opération qui prend du temps et ne fait pas appel au runtime, elle doit vérifier périodiquement l'annulation et s'arrêter de façon opportune.L'exemple suivant illustre une manière de déterminer quand le travail est annulé.La tâche t4 annule le groupe de tâches parent lorsqu'elle rencontre une erreur.La tâche t5 appelle parfois la méthode structured_task_group::is_canceling afin de vérifier l'annulation.Si le groupe de tâches parent est annulé, la tâche t5 imprime un message et quitte.

structured_task_group tg2;

// Create a child task.
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }
});

// Create a child task.
auto t5 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // To reduce overhead, occasionally check for 
      // cancelation.
      if ((i%100) == 0)
      {
         if (tg2.is_canceling())
         {
            wcout << L"The task was canceled." << endl;
            break;
         }
      }

      // TODO: Perform work here.
   }
});

// Run the child tasks and wait for them to finish.
tg2.run(t4);
tg2.run(t5);
tg2.wait();

Cet exemple vérifie l'annulation chaque 100ème itération de la boucle de tâche.La fréquence à laquelle vous vérifiez l'annulation dépend de la quantité de travail effectuée par votre tâche et de la rapidité avec laquelle les tâches doivent répondre à l'annulation.

Si vous n'avez pas accès à l'objet groupe de tâches parent, appelez la fonction de concurrency::is_current_task_group_canceling pour déterminer si le groupe de tâches parent est annulé.

La méthode cancel affecte seulement les tâches enfants.Par exemple, si vous annulez le groupe de tâches tg1 dans l'illustration de l'arborescence de travail parallèle, toutes les tâches dans l'arborescence (t1, t2, t3, t4 et t5) sont affectées.Si vous annulez le groupe de tâches imbriqué, tg2, seules les tâches t4 et t5 sont affectées.

Lorsque vous appelez la méthode cancel, tous les groupes de tâches enfants sont également annulés.Toutefois, l'annulation n'affecte aucun parent du groupe de tâches dans une arborescence de travail parallèle.Les exemples suivants, qui sont basés sur l'illustration de l'arborescence de travail parallèle, illustrent ce comportement.

Le premier de ces exemples crée une fonction de travail pour la tâche t4, qui est un enfant du groupe de tâches tg2.La fonction de travail appelle la fonction work en boucle.En cas d'échec d'un appel à work, la tâche annule son groupe de tâches parent.Cela provoque le basculement du groupe de tâches tg2 à l'état annulé, mais n'annule pas le groupe de tâches tg1.

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel the parent task
      // and break from the loop.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg2.cancel();
         break;
      }
   }         
});

Ce deuxième exemple ressemble au premier, mais la tâche annule le groupe de tâches tg1.Cela affecte toutes les tâches dans l'arborescence (t1, t2, t3, t4 et t5).

auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, cancel all tasks in the tree.
      bool succeeded = work(i);
      if (!succeeded)
      {
         tg1.cancel();
         break;
      }
   }   
});

La classe structured_task_group n'est pas thread-safe.Par conséquent, une tâche enfant qui appelle une méthode de son objet structured_task_group parent produit un comportement non spécifié.Les exceptions à cette règle sont les méthodes d' 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 et le contrôle parents pour l'annulation.

Mise en gardeAttention

Bien que vous puissiez utiliser un jeton d'annulation pour annuler le travail exécuté par un groupe de tâches qui s'exécute en tant qu'enfant d'un objet d' task , vous ne pouvez pas utiliser les méthodes d' task_group::cancel ou d' structured_task_group::cancel pour annuler les objets d' task qui s'exécutent à un groupe de tâches.

[Supérieur]

Dd984117.collapse_all(fr-fr,VS.110).gifUtilisation d'exceptions pour annuler du travail parallèle

L'utilisation de jetons d'annulation et la méthode d' cancel sont plus efficaces que la gestion des exceptions pour annuler une arborescence de travail parallèle.Les jetons d'annulation et la méthode d' cancel annulent une tâche et toutes les tâches enfants de haut en bas.Inversement, la gestion des exceptions opère de bas en haut et doit annuler chaque groupe de tâches enfant indépendamment à mesure que l'exception se propage de bas en haut.La rubrique Gestion des exceptions dans le runtime d'accès concurrentiel explique comment le runtime d'accès concurrentiel utilise des exceptions pour signaler des erreurs.Toutefois, les exceptions n'indiquent pas toutes une erreur.Par exemple, un algorithme de recherche peut annuler la tâche associée lorsqu'il trouve le résultat.Toutefois, comme indiqué précédemment, la gestion des exceptions est moins efficace que l'utilisation de la méthode cancel pour annuler du travail parallèle.

Mise en gardeAttention

Nous vous conseillons d'utiliser des exceptions pour annuler du travail parallèle uniquement si nécessaire.Les jetons d'annulation et les méthodes d' cancel de groupe de tâches sont une erreur plus efficace et moins encline.

Lorsque vous levez une exception dans le corps d'une fonction de travail que vous passez à un groupe de tâches, le runtime stocke cette exception et la marshale au contexte qui attend que le groupe de tâches se termine.Comme avec la méthode cancel, le runtime ignore les tâches qui n'ont pas encore commencé et n'accepte pas de nouvelles tâches.

Ce troisième exemple ressemble au deuxième, mais la tâche t4 lève une exception pour annuler le groupe de tâches tg2.Cet exemple utilise un bloc try-catch pour vérifier l'annulation lorsque le groupe de tâches tg2 attend la fin de l'exécution de ses tâches enfants.Comme le premier exemple, cela provoque le basculement du groupe de tâches tg2 à l'état annulé, mais n'annule pas le groupe de tâches tg1.

structured_task_group tg2;

// Create a child task.      
auto t4 = make_task([&] {
   // Perform work in a loop.
   for (int i = 0; i < 1000; ++i)
   {
      // Call a function to perform work.
      // If the work function fails, throw an exception to 
      // cancel the parent task.
      bool succeeded = work(i);
      if (!succeeded)
      {
         throw exception("The task failed");
      }
   }         
});

// Create a child task.
auto t5 = make_task([&] {
   // TODO: Perform work here.
});

// Run the child tasks.
tg2.run(t4);
tg2.run(t5);

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg2.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

Ce quatrième exemple utilise la gestion des exceptions pour annuler l'ensemble de l'arborescence de travail.L'exemple intercepte l'exception lorsque le groupe de tâches tg1 attend la fin de l'exécution de ses tâches enfants, plutôt que lorsque le groupe de tâches tg2 attend ses tâches enfants.Comme le deuxième exemple, cela provoque le basculement à l'état annulé des deux groupes de tâches dans l'arborescence, tg1 et tg2.

// Run the child tasks.
tg1.run(t1);
tg1.run(t2);
tg1.run(t3);   

// Wait for the tasks to finish. The runtime marshals any exception
// that occurs to the call to wait.
try
{
   tg1.wait();
}
catch (const exception& e)
{
   wcout << e.what() << endl;
}

Étant donné que les méthodes structured_task_group::wait et task_group::wait lèvent une exception lorsqu'une tâche enfant lève une exception, elles ne fournissent pas de valeur de retour.

[Supérieur]

Annulation d'algorithmes parallèles

Algorithmes parallèles dans la bibliothèque PPL, par exemple, parallel_for, génération sur les groupes de tâches.Par conséquent, vous pouvez utiliser une grande partie des mêmes techniques pour annuler un algorithme parallèle.

Les exemples suivants illustrent plusieurs manières d'annuler un algorithme parallèle.

L'exemple suivant utilise la fonction d' run_with_cancellation_token pour appeler l'algorithme d' parallel_for .La fonction d' run_with_cancellation_token prend un jeton d'annulation comme argument et appelle la fonction de travail fournie de façon synchrone.Étant donné que les algorithmes parallèles sont générés sur les tâches, ils héritent jeton d'annulation de la tâche parente.Par conséquent, parallel_for peut répondre à l'annulation.

// cancel-parallel-for.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Call parallel_for in the context of a cancellation token.
    cancellation_token_source cts;
    run_with_cancellation_token([&cts]() 
    {
        // Print values to the console in parallel.
        parallel_for(0, 20, [&cts](int n)
        {
            // For demonstration, cancel the overall operation 
            // when n equals 11.
            if (n == 11)
            {
                cts.cancel();
            }
            // Otherwise, print the value.
            else
            {
                wstringstream ss;
                ss << n << endl;
                wcout << ss.str();
            }
        });
    }, cts.get_token());
}
/* Sample output:
    15
    16
    17
    10
    0
    18
    5
*/

L'exemple suivant utilise la méthode de concurrency::structured_task_group::run_and_wait pour appeler l'algorithme d' parallel_for .La méthode structured_task_group::run_and_wait attend que la tâche fournie se termine.L'objet structured_task_group permet à la fonction de travail d'annuler la tâche.

// To enable cancelation, call parallel_for in a task group.
structured_task_group tg;

task_group_status status = tg.run_and_wait([&] {
   parallel_for(0, 100, [&](int i) {
      // Cancel the task when i is 50.
      if (i == 50)
      {
         tg.cancel();
      }
      else
      {
         // TODO: Perform work here.
      }
   });
});

// Print the task group status.
wcout << L"The task group status is: ";
switch (status)
{
case not_complete:
   wcout << L"not complete." << endl;
   break;
case completed:
   wcout << L"completed." << endl;
   break;
case canceled:
   wcout << L"canceled." << endl;
   break;
default:
   wcout << L"unknown." << endl;
   break;
}

Cet exemple génère la sortie suivante.

The task group status is: canceled.

L'exemple suivant utilise la gestion des exceptions pour annuler une boucle parallel_for.Le runtime marshale l'exception au contexte appelant.

try
{
   parallel_for(0, 100, [&](int i) {
      // Throw an exception to cancel the task when i is 50.
      if (i == 50)
      {
         throw i;
      }
      else
      {
         // TODO: Perform work here.
      }
   });
}
catch (int n)
{
   wcout << L"Caught " << n << endl;
}

Cet exemple génère la sortie suivante.

Caught 50

L'exemple suivant utilise un indicateur Boolean pour coordonner l'annulation dans une boucle parallel_for.Chaque tâche s'exécute car cet exemple n'utilise ni la méthode cancel ni la gestion des exceptions pour annuler le jeu entier de tâches.Par conséquent, cette technique peut imposer une charge de calcul supérieure à un mécanisme d'annulation.

// Create a Boolean flag to coordinate cancelation.
bool canceled = false;

parallel_for(0, 100, [&](int i) {
   // For illustration, set the flag to cancel the task when i is 50.
   if (i == 50)
   {
      canceled = true;
   }

   // Perform work if the task is not canceled.
   if (!canceled)
   {
      // TODO: Perform work here.
   }
});

Chaque méthode d'annulation présente des avantages par rapport aux autres.Choisissez la méthode qui correspond leur mieux à vos besoins spécifiques.

[Supérieur]

Quand ne pas utiliser l'annulation

L'utilisation de l'annulation est appropriée lorsque chaque membre d'un groupe de tâches connexes peut quitter de façon opportune.Toutefois, il existe des scénarios où l'annulation peut ne pas convenir à votre application.Par exemple, l'annulation de tâche étant coopérative, le jeu entier de tâches n'est pas annulé si l'une des tâches est bloquée.Par exemple, si une tâche n'a pas encore commencé, mais qu'elle débloque une autre tâche active, elle ne démarre pas si le groupe de tâches est annulé.Cela peut provoquer un interblocage dans votre application.Voici un second exemple illustrant le fait que l'utilisation de l'annulation peut ne pas convenir : lorsqu'une tâche est annulée, mais que sa tâche enfant effectue une opération importante, telle que la libération d'une ressource.Étant donné que le jeu entier de tâches est annulé lorsque la tâche parente est annulée, cette opération ne s'exécutera pas.Pour obtenir un exemple illustrant cet élément, consultez la section Understand how Cancellation and Exception Handling Affect Object Destruction des meilleures pratiques de la rubrique relative aux bibliothèques de modèles parallèles (PPL).

[Supérieur]

Rubriques connexes

Titre

Description

Comment : utiliser l'annulation pour rompre une boucle parallèle

Indique comment utiliser l'annulation pour implémenter un algorithme de recherche parallèle.

Comment : utiliser la gestion des exceptions pour rompre une boucle parallèle

Montre comment utiliser la classe d' task_group pour écrire un algorithme de recherche pour une arborescence de base.

Gestion des exceptions dans le runtime d'accès concurrentiel

Décrit comment le runtime gère les exceptions levées par des groupes de tâches, des tâches légères et des agents asynchrones, et comment répondre aux exceptions dans vos applications.

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

Décrit les relations entre les tâches et les groupes de tâches, et comment utiliser des tâches structurées et non structurées dans vos applications.

Algorithmes parallèles

Décrit les algorithmes parallèles, qui exécutent de manière simultanée du travail sur des collections de données

Bibliothèque de modèles parallèles

Fournit une vue d'ensemble de la Bibliothèque de modèles parallèles.

Référence

task (Concurrency Runtime), classe

cancellation_token_source, classe

cancellation_token, classe

task_group, classe

structured_task_group, classe

parallel_for, fonction