Tutorial: Implementar futuros

En este tema se muestra cómo implementar futuros en la aplicación. En el tema se muestra cómo combinar la funcionalidad existente en el Runtime de simultaneidad para conseguir algo que hace más.

Importante

En este tema se muestra el concepto de futuros para fines de demostración. Se recomienda usar std::future o concurrency::task cuando necesite una tarea asincrónica que calcule un valor para su uso posterior.

Una tarea es un cálculo que se puede descomponer en otros cálculos más específicos. Un futuro es una tarea asincrónica que calcula un valor para su uso posterior.

Para implementar futuros, este tema define la clase async_future. La clase async_future usa los siguientes componentes del runtime de simultaneidad: la clase concurrency::task_group y la clase concurrency::single_assignment. La clase async_future utiliza la clase task_group para calcular un valor de forma asincrónica y la clase single_assignment para almacenar el resultado del cálculo. El constructor de la clase async_future toma una función de trabajo que calcula el resultado, y el método get recupera el resultado.

Para implementar la clase async_future

  1. Declare una clase de plantilla con el nombre async_future que se parametrice en el tipo del cálculo resultante. Agregue secciones public y private a esta clase.
template <typename T>
class async_future
{
public:
private:
};
  1. En la sección private de la clase async_future, declare miembros de datos task_group y single_assignment.
// Executes the asynchronous work function.
task_group _tasks;

// Stores the result of the asynchronous work function.
single_assignment<T> _value;
  1. En la sección public de la clase async_future, implemente el constructor. El constructor es una plantilla que se parametriza en la función de trabajo que calcula el resultado. El constructor ejecuta de forma asincrónica la función de trabajo en el miembro de datos task_group y usa la función concurrency::send para escribir el resultado en el miembro de datos single_assignment.
template <class Functor>
explicit async_future(Functor&& fn)
{
   // Execute the work function in a task group and send the result
   // to the single_assignment object.
   _tasks.run([fn, this]() {
      send(_value, fn());
    });
}
  1. En la sección public de la clase async_future, implemente el destructor. El destructor espera hasta que finaliza la tarea.
~async_future()
{
   // Wait for the task to finish.
   _tasks.wait();
}
  1. En la sección public de la clase async_future, implemente el método get. Este método usa la función concurrency::receive para recuperar el resultado de la función de trabajo.
// Retrieves the result of the work function.
// This method blocks if the async_future object is still 
// computing the value.
T get()
{ 
   return receive(_value); 
}

Ejemplo

Descripción

En el siguiente ejemplo se muestran la clase async_future completa y un ejemplo de su uso. La función wmain crea un objeto std::vector que contiene 10.000 valores enteros aleatorios. A continuación, utiliza objetos async_future para encontrar los valores mayor y menor incluidos en el objeto vector.

Código

// futures.cpp
// compile with: /EHsc
#include <ppl.h>
#include <agents.h>
#include <vector>
#include <algorithm>
#include <iostream>
#include <numeric>
#include <random>

using namespace concurrency;
using namespace std;

template <typename T>
class async_future
{
public:
   template <class Functor>
   explicit async_future(Functor&& fn)
   {
      // Execute the work function in a task group and send the result
      // to the single_assignment object.
      _tasks.run([fn, this]() {
         send(_value, fn());
       });
   }

   ~async_future()
   {
      // Wait for the task to finish.
      _tasks.wait();
   }

   // Retrieves the result of the work function.
   // This method blocks if the async_future object is still 
   // computing the value.
   T get()
   { 
      return receive(_value); 
   }

private:
   // Executes the asynchronous work function.
   task_group _tasks;

   // Stores the result of the asynchronous work function.
   single_assignment<T> _value;
};

int wmain()
{
   // Create a vector of 10000 integers, where each element 
   // is between 0 and 9999.
   mt19937 gen(2);
   vector<int> values(10000);   
   generate(begin(values), end(values), [&gen]{ return gen()%10000; });

   // Create a async_future object that finds the smallest value in the
   // vector.
   async_future<int> min_value([&]() -> int { 
      int smallest = INT_MAX;
      for_each(begin(values), end(values), [&](int value) {
         if (value < smallest)
         {
            smallest = value;
         }
      });
      return smallest;
   });
   
   // Create a async_future object that finds the largest value in the
   // vector.
   async_future<int> max_value([&]() -> int { 
      int largest = INT_MIN;
      for_each(begin(values), end(values), [&](int value) {
         if (value > largest)
         {
            largest = value;
         } 
      });
      return largest;
   });

   // Calculate the average value of the vector while the async_future objects
   // work in the background.
   int sum = accumulate(begin(values), end(values), 0);
   int average = sum / values.size();

   // Print the smallest, largest, and average values.
   wcout << L"smallest: " << min_value.get() << endl
         << L"largest:  " << max_value.get() << endl
         << L"average:  " << average << endl;
}

Comentarios

Este ejemplo produce el siguiente resultado:

smallest: 0
largest:  9999
average:  4981

En el ejemplo se usa el método async_future::get para recuperar los resultado del cálculo. El método async_future::get espera hasta que finaliza el cálculo si este está todavía activo.

Programación sólida

Para extender la clase async_future a fin de controlar las excepciones que inicia la función de trabajo, modifique el método async_future::get para llamar al método concurrency::task_group::wait. El método task_group::wait inicia las excepciones que genera la función de trabajo.

En el ejemplo siguiente se muestra la versión modificada de la clase async_future: La función wmain utiliza un bloque try-catch para imprimir el resultado del objeto async_future o imprimir el valor de la excepción que genera la función de trabajo.

// futures-with-eh.cpp
// compile with: /EHsc
#include <ppl.h>
#include <agents.h>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace concurrency;
using namespace std;

template <typename T>
class async_future
{
public:
   template <class Functor>
   explicit async_future(Functor&& fn)
   {
      // Execute the work function in a task group and send the result
      // to the single_assignment object.
      _tasks.run([fn, this]() {
         send(_value, fn());
       });
   }

   ~async_future()
   {
      // Wait for the task to finish.
      _tasks.wait();
   }

   // Retrieves the result of the work function.
   // This method blocks if the async_future object is still
   // computing the value.
   T get()
   { 
      // Wait for the task to finish.
      // The wait method throws any exceptions that were generated
      // by the work function.
      _tasks.wait();

      // Return the result of the computation.
      return receive(_value);
   }

private:
   // Executes the asynchronous work function.
   task_group _tasks;

   // Stores the result of the asynchronous work function.
   single_assignment<T> _value;
};

int wmain()
{
   // For illustration, create a async_future with a work 
   // function that throws an exception.
   async_future<int> f([]() -> int { 
      throw exception("error");
   });

   // Try to read from the async_future object. 
   try
   {
      int value = f.get();
      wcout << L"f contains value: " << value << endl;
   }
   catch (const exception& e)
   {
      wcout << L"caught exception: " << e.what() << endl;
   }
}

Este ejemplo produce el siguiente resultado:

caught exception: error

Para obtener más información sobre el modelo de control de excepciones en el Runtime de simultaneidad, vea Control de excepciones.

Compilar el código

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

cl.exe /EHsc futures.cpp

Consulte también

Tutoriales del Runtime de simultaneidad
Control de excepciones
task_group (clase)
single_assignment (clase)