Condividi tramite


Procedura: utilizzare la gestione delle eccezion per interrompere un ciclo Parallel

In questo argomento viene illustrato come scrivere un algoritmo di ricerca per una struttura ad albero di base.

Nell'argomento Annullamento nella libreria PPL viene illustrato il ruolo dell'annullamento nella libreria PPL (Parallel Patterns Library). L'utilizzo della gestione delle eccezioni rappresenta il modo meno efficiente per annullare un lavoro parallelo rispetto all'utilizzo dei metodi Concurrency::task_group::cancel e Concurrency::structured_task_group::cancel. Tuttavia, uno scenario in cui l'utilizzo della gestione delle eccezioni per annullare un lavoro risulta appropriato è quando si effettua una chiamata in una libreria di terze parti che utilizza attività o algoritmi paralleli ma non fornisce un oggetto task_group or structured_task_group per l'annullamento.

Esempio

Nell'esempio seguente viene illustrato un tipo tree di base che contiene un elemento dati e un elenco di nodi figlio. Nella sezione seguente viene illustrato il corpo del metodo for_all, che esegue in modo ricorsivo una funzione lavoro in ogni nodo figlio.

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

Nell'esempio seguente viene illustrato il metodo for_all. Viene utilizzato l'algoritmo Concurrency::parallel_for_each per eseguire una funzione lavoro in ogni nodo dell'albero in parallelo.

// 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(_children.begin(), _children.end(), [&](tree& child) {
      child.for_all(action);
   });

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

Nell'esempio seguente viene illustrata la funzione search_for_value che cerca un valore nell'oggetto tree fornito. Questa funzione passa al metodo for_all una funzione lavoro generata quando trova un nodo della struttura ad albero che contiene il valore fornito.

Si supponga che la classe tree venga fornita da una libreria di terze parti e che non sia possibile modificarla. In questo caso, l'utilizzo della gestione delle eccezioni è appropriato poiché il metodo for_all non fornisce un oggetto task_group o structured_task_group al chiamante. Pertanto, la funzione lavoro non è in grado di annullare direttamente il gruppo di attività padre.

Quando la funzione lavoro fornita a un gruppo di attività genera un'eccezione, il runtime arresta tutte le attività presenti nel gruppo di attività, compresi i gruppi di attività figlio, ed elimina le attività che non sono ancora state avviate. La funzione search_for_value utilizza un blocco try-catch per acquisire l'eccezione e visualizzare il risultato nella 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();   
}

Nell'esempio seguente viene creato un oggetto tree, in cui vengono ricercati diversi valori in parallelo. La funzione build_tree viene illustrata più avanti in questo argomento.

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

In questo esempio viene utilizzato l'algoritmo Concurrency::parallel_invoke per cercare i valori in parallelo. Per ulteriori informazioni su questo algoritmo, vedere Algoritmi paralleli.

Nell'esempio completo seguente viene utilizzata la gestione delle eccezioni per cercare i valori in una struttura ad albero di 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(_children.begin(), _children.end(), [&](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); }
   );
}

Questo esempio produce l'output seguente:

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

Compilazione del codice

Copiare il codice di esempio e incollarlo in un progetto di Visual Studio o incollarlo in un file denominato task-tree-search.cpp, quindi eseguire il comando seguente in una finestra del prompt dei comandi di Visual Studio 2010.

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

Vedere anche

Riferimenti

Classe task_group

Classe structured_task_group

Funzione parallel_for_each

Concetti

Annullamento nella libreria PPL

Gestione delle eccezioni nel runtime di concorrenza

Parallelismo delle attività (runtime di concorrenza)

Algoritmi paralleli

Cronologia delle modifiche

Data

Cronologia

Motivo

Marzo 2011

Aggiunta di un chiarimento per spiegare quando utilizzare la gestione delle eccezioni per annullare un'operazione in parallelo.

Commenti e suggerimenti dei clienti.