Compartir a través de


Cómo: Usar el control de excepciones para interrumpir un bucle Parallel

En este tema se muestra cómo escribir un algoritmo de búsqueda en una estructura de árbol básica.

En el tema Cancelación se explica el rol de cancelación en la Biblioteca de patrones paralelos. El uso del control de excepciones es una manera menos eficaz de cancelar el trabajo paralelo que el uso de los métodos concurrency::task_group::cancel y concurrency::structured_task_group::cancel. Sin embargo, un escenario en el que el uso del control de excepciones para cancelar el trabajo es adecuado es cuando se llama a una biblioteca de terceros que usa tareas o algoritmos paralelos, pero no proporciona un objeto task_group o structured_task_group para cancelar.

Ejemplo: tipo de árbol básico

En el ejemplo siguiente se muestra un tipo tree básico que contiene un elemento de datos y una lista de nodos secundarios. En la sección siguiente se muestra el cuerpo del método for_all, que realiza de forma recurrente una función de trabajo en cada nodo secundario.

// 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;
};

Ejemplo: realizar trabajo en paralelo

El ejemplo siguiente muestra el método for_all. Usa el algoritmo concurrency::parallel_for_each para realizar una función de trabajo en cada nodo del árbol en paralelo.

// 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);
}

Ejemplo: buscar un valor en el árbol

El ejemplo siguiente se muestra la función search_for_value, que busca un valor en el objeto tree proporcionado. Esta función pasa al método for_all una función de trabajo que se produce cuando encuentra un nodo de árbol que contiene el valor proporcionado.

Supongamos que una biblioteca de terceros proporciona la clase tree y que no se puede modificar. En este caso, el uso del control de excepciones es adecuado porque el método for_all no proporciona un objeto task_group o structured_task_group al autor de la llamada. Por lo tanto, la función de trabajo no puede cancelar directamente su grupo de tareas primario.

Cuando la función de trabajo que se proporciona a un grupo de tareas produce una excepción, el tiempo de ejecución detiene todas las tareas que se encuentran en el grupo de tareas (incluidos los grupos de tareas secundarios) y descarta las tareas que aún no se han iniciado. La función search_for_value usa un bloque try-catch para capturar la excepción e imprimir el resultado en la consola.

// 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();   
}

Ejemplo: creación y búsqueda de un árbol en paralelo

En el ejemplo siguiente se crea un objeto tree y se buscan varios valores en paralelo. La función build_tree se muestra más adelante en este tema.

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); }
   );
}

En este ejemplo se usa el algoritmo concurrency::parallel_invoke para buscar valores en paralelo. Para obtener más información acerca de este algoritmo, consulte Algoritmos paralelos.

Ejemplo: ejemplo de código de control de excepciones finalizada

En el siguiente ejemplo completo se usa el control de excepciones para buscar valores en una estructura de árbol básica.

// 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); }
   );
}

Este ejemplo genera la siguiente salida de ejemplo.

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

Compilar el código

Copie el código de ejemplo y péguelo en un proyecto de Visual Studio o en un archivo denominado task-tree-search.cpp y, después, ejecute el siguiente comando en una ventana del símbolo del sistema de Visual Studio.

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

Consulte también

Cancelación en la biblioteca PPL
Control de excepciones
Paralelismo de tareas
Algoritmos paralelos
task_group (clase)
structured_task_group (clase)
función parallel_for_each