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

Cette rubrique montre comment écrire un algorithme de recherche pour une structure d’arborescence de base.

La rubrique Annulation explique le rôle d’annulation dans la bibliothèque de modèles parallèles. L’utilisation de la gestion des exceptions est un moyen moins efficace d’annuler le travail parallèle que l’utilisation des méthodes concurrency ::task_group ::cancel et concurrency ::structured_task_group ::cancel . Toutefois, un scénario dans lequel l’utilisation de la gestion des exceptions pour annuler le travail est appropriée lorsque vous appelez une bibliothèque tierce qui utilise des tâches ou des algorithmes parallèles, mais ne fournit pas d’objet ou structured_task_group d’annulationtask_group.

Exemple : Type d’arborescence de base

L’exemple suivant montre un type de base tree qui contient un élément de données et une liste de nœuds enfants. La section suivante montre le corps de la for_all méthode, qui effectue de manière récursive une fonction de travail sur chaque nœud enfant.

// A simple tree structure that has multiple child nodes.
template <typename T>
class tree
{
public:
   explicit tree(T data)
      : _data(data)
   {
   }

   // Retrieves the data element for the node. 
   T get_data() const
   {
      return _data;
   }

   // Adds a child node to the tree.
   void add_child(tree& child)
   {
      _children.push_back(child);
   }

   // Performs the given work function on the data element of the tree and
   // on each child.
   template<class Function>
   void for_all(Function& action);

private:
   // The data for this node.
   T _data;
   // The child nodes.
   list<tree> _children;
};

Exemple : Effectuer un travail en parallèle

L’exemple suivant montre la for_all méthode. Il utilise l’algorithme concurrency ::p arallel_for_each pour effectuer une fonction de travail sur chaque nœud de l’arborescence en parallèle.

// Performs the given work function on the data element of the tree and
// on each child.
template<class Function>
void for_all(Function& action)
{
   // Perform the action on each child.
   parallel_for_each(begin(_children), end(_children), [&](tree& child) {
      child.for_all(action);
   });

   // Perform the action on this node.
   action(*this);
}

Exemple : rechercher une valeur dans l’arborescence

L'exemple suivant illustre la fonction search_for_value, qui recherche une valeur dans l'objet tree fourni. Cette fonction passe à la for_all méthode une fonction de travail qui lève lorsqu’elle trouve un nœud d’arborescence qui contient la valeur fournie.

Supposons que la tree classe est fournie par une bibliothèque tierce et que vous ne pouvez pas la modifier. Dans ce cas, l’utilisation de la gestion des exceptions est appropriée, car la for_all méthode ne fournit pas de ou structured_task_group d’objet task_group à l’appelant. Par conséquent, la fonction de travail ne peut pas annuler directement son groupe de tâches parent.

Lorsque la fonction de travail que vous fournissez à un groupe de tâches lève une exception, le runtime arrête toutes les tâches qui se trouvent dans le groupe de tâches (y compris les groupes de tâches enfants) et dés carte toutes les tâches qui n’ont pas encore démarré. La fonction search_for_value utilise un bloc try-catch pour capturer l'exception et imprimer le résultat dans la console.

// Searches for a value in the provided tree object.
template <typename T>
void search_for_value(tree<T>& t, int value)
{
   try
   {
      // Call the for_all method to search for a value. The work function
      // throws an exception when it finds the value.
      t.for_all([value](const tree<T>& node) {
         if (node.get_data() == value)
         {
            throw &node;
         }
      });
   }
   catch (const tree<T>* node)
   {
      // A matching node was found. Print a message to the console.
      wstringstream ss;
      ss << L"Found a node with value " << value << L'.' << endl;
      wcout << ss.str();
      return;
   }

   // A matching node was not found. Print a message to the console.
   wstringstream ss;
   ss << L"Did not find node with value " << value << L'.' << endl;
   wcout << ss.str();   
}

Exemple : Créer et rechercher une arborescence en parallèle

L’exemple suivant crée un tree objet et recherche plusieurs valeurs en parallèle. La build_tree fonction est affichée plus loin dans cette rubrique.

int wmain()
{  
   // Build a tree that is four levels deep with the initial level 
   // having three children. The value of each node is a random number.
   mt19937 gen(38);
   tree<int> t = build_tree<int>(4, 3, [&gen]{ return gen()%100000; });

   // Search for a few values in the tree in parallel.
   parallel_invoke(
      [&t] { search_for_value(t, 86131); },
      [&t] { search_for_value(t, 17522); },
      [&t] { search_for_value(t, 32614); }
   );
}

Cet exemple utilise l’algorithme concurrency ::p arallel_invoke pour rechercher des valeurs en parallèle. Pour plus d’informations sur cet algorithme, consultez Algorithmes parallèles.

Exemple : Exemple de code de gestion des exceptions terminé

L’exemple complet suivant utilise la gestion des exceptions pour rechercher des valeurs dans une arborescence de base.

// task-tree-search.cpp
// compile with: /EHsc
#include <ppl.h>
#include <list>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <random>

using namespace concurrency;
using namespace std;

// A simple tree structure that has multiple child nodes.
template <typename T>
class tree
{
public:
   explicit tree(T data)
      : _data(data)
   {
   }

   // Retrieves the data element for the node. 
   T get_data() const
   {
      return _data;
   }

   // Adds a child node to the tree.
   void add_child(tree& child)
   {
      _children.push_back(child);
   }

   // Performs the given work function on the data element of the tree and
   // on each child.
   template<class Function>
   void for_all(Function& action)
   {
      // Perform the action on each child.
      parallel_for_each(begin(_children), end(_children), [&](tree& child) {
         child.for_all(action);
      });

      // Perform the action on this node.
      action(*this);
   }

private:
   // The data for this node.
   T _data;
   // The child nodes.
   list<tree> _children;
};

// Builds a tree with the given depth. 
// Each node of the tree is initialized with the provided generator function.
// Each level of the tree has one more child than the previous level.
template <typename T, class Generator>
tree<T> build_tree(int depth, int child_count, Generator& g)
{
   // Create the tree node.
   tree<T> t(g());

   // Add children.
   if (depth > 0)
   {
      for(int i = 0; i < child_count; ++i)
      {
         t.add_child(build_tree<T>(depth - 1, child_count + 1, g));
      }
   }

   return t;
}

// Searches for a value in the provided tree object.
template <typename T>
void search_for_value(tree<T>& t, int value)
{
   try
   {
      // Call the for_all method to search for a value. The work function
      // throws an exception when it finds the value.
      t.for_all([value](const tree<T>& node) {
         if (node.get_data() == value)
         {
            throw &node;
         }
      });
   }
   catch (const tree<T>* node)
   {
      // A matching node was found. Print a message to the console.
      wstringstream ss;
      ss << L"Found a node with value " << value << L'.' << endl;
      wcout << ss.str();
      return;
   }

   // A matching node was not found. Print a message to the console.
   wstringstream ss;
   ss << L"Did not find node with value " << value << L'.' << endl;
   wcout << ss.str();   
}

int wmain()
{  
   // Build a tree that is four levels deep with the initial level 
   // having three children. The value of each node is a random number.
   mt19937 gen(38);
   tree<int> t = build_tree<int>(4, 3, [&gen]{ return gen()%100000; });

   // Search for a few values in the tree in parallel.
   parallel_invoke(
      [&t] { search_for_value(t, 86131); },
      [&t] { search_for_value(t, 17522); },
      [&t] { search_for_value(t, 32614); }
   );
}

Cet exemple génère l’exemple de sortie suivant.

Found a node with value 32614.
Found a node with value 86131.
Did not find node with value 17522.

Compilation du code

Copiez l’exemple de code et collez-le dans un projet Visual Studio, ou collez-le dans un fichier nommé task-tree-search.cpp , puis exécutez la commande suivante dans une fenêtre d’invite de commandes Visual Studio.

cl.exe /EHsc task-tree-search.cpp

Voir aussi

Annulation dans la bibliothèque de modèles parallèles
Gestion des exceptions
Parallélisme des tâches
Algorithmes parallèles
Classe task_group
structured_task_group, classe
fonction parallel_for_each