Exemplarische Vorgehensweise: Implementieren von Futures

In diesem Thema erfahren Sie, wie Sie Futures in Ihre Anwendung implementieren. Es wird veranschaulicht, wie Sie die vorhandenen Funktionen in der Concurrency Runtime kombinieren können, um mehr Funktionalität zu erzielen.

Wichtig

In diesem Thema wird das Konzept von Futures zu Demonstrationszwecken veranschaulicht. Es wird empfohlen, "std::future" oder "concurrency::task" zu verwenden, wenn Sie eine asynchrone Aufgabe benötigen, die einen Wert für die spätere Verwendung berechnet.

Bei einer Aufgabe handelt es sich um eine Berechnung, die in zusätzliche, feinkörnige Berechnungen zerlegt werden kann. Eine Zukunft ist eine asynchrone Aufgabe, die einen Wert für die spätere Verwendung berechnet.

Zur Implementierung von Futures wird in diesem Thema die async_future-Klasse definiert. Die async_future Klasse verwendet diese Komponenten der Concurrency Runtime: die Concurrency::task_group-Klasse und die Concurrency::single_assignment-Klasse . Die async_future-Klasse verwendet die task_group-Klasse zur asynchronen Berechnung eines Werts und die single_assignment-Klasse zum Speichern des Ergebnisses. Der Konstruktor der async_future-Klasse akzeptiert eine Arbeitsfunktion, die das Ergebnis berechnet, das mit der get-Methode abgerufen wird.

So implementieren Sie die async_future-Klasse

  1. Deklarieren Sie eine Vorlagenklasse mit dem Namen async_future, die auf der Grundlage des Typs der resultierenden Berechnung parametrisiert wird. Fügen Sie der Klasse einen public-Abschnitt und einen private-Abschnitt hinzu.
template <typename T>
class async_future
{
public:
private:
};
  1. Deklarieren Sie im private-Abschnitt der async_future-Klasse ein task_group-Datenmember und ein single_assignment-Datenmember.
// Executes the asynchronous work function.
task_group _tasks;

// Stores the result of the asynchronous work function.
single_assignment<T> _value;
  1. Implementieren Sie im public-Abschnitt der async_future-Klasse den Konstruktor. Der Konstruktor ist eine Vorlage, die auf Grundlage der Arbeitsfunktion, die zur Berechnung des Ergebnisses dient, parametrisiert wird. Der Konstruktor führt die Arbeitsfunktion im task_group Datenmemm asynchron aus und verwendet die Parallelität::Send-Funktion , um das Ergebnis in das single_assignment Datenelement zu schreiben.
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. Implementieren Sie im public-Abschnitt der async_future-Klasse den Destruktor. Der Destruktor wartet auf das Beenden der Aufgabe.
~async_future()
{
   // Wait for the task to finish.
   _tasks.wait();
}
  1. Implementieren Sie im public-Abschnitt der async_future-Klasse die get-Methode. Diese Methode verwendet die Funktion "concurrency::receive " zum Abrufen des Ergebnisses der Arbeitsfunktion.
// 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); 
}

Beispiel

BESCHREIBUNG

Das folgende Beispiel zeigt die vollständige async_future-Klasse mit einer Verwendungsmöglichkeit. Die wmain Funktion erstellt ein std::vector-Objekt , das 10.000 zufällige ganzzahlige Werte enthält. Anschließend werden mithilfe von async_future-Objekten der kleinste und der größte Wert im vector-Objekt gesucht.

Code

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

Kommentare

Dieses Beispiel erzeugt die folgende Ausgabe:

smallest: 0
largest:  9999
average:  4981

Im Beispiel werden die Ergebnisse der Berechnung mit der async_future::get-Methode abgerufen. Die async_future::get-Methode wartet, bis die Berechnung beendet ist.

Stabile Programmierung

Um die async_future Klasse so zu erweitern, dass Ausnahmen verarbeitet werden, die von der Arbeitsfunktion ausgelöst werden, ändern Sie die async_future::get Methode, um die Parallelität::task_group::wait-Methode aufzurufen. Die task_group::wait-Methode löst alle von der Arbeitsfunktion generierten Ausnahmen aus.

Das folgende Beispiel zeigt die geänderte Version der async_future-Klasse. Die wmain Funktion verwendet einen try-catch Block zum Drucken des Ergebnisses des async_future Objekts oder zum Drucken des Werts der Ausnahme, die von der Arbeitsfunktion generiert wird.

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

Dieses Beispiel erzeugt die folgende Ausgabe:

caught exception: error

Weitere Informationen zum Ausnahmebehandlungsmodell in der Parallelitätslaufzeit finden Sie unter Exception Handling.

Kompilieren des Codes

Kopieren Sie den Beispielcode, fügen Sie ihn in ein Visual Studio-Projekt ein, oder fügen Sie ihn in eine Datei ein, die benannt futures.cpp ist, und führen Sie dann den folgenden Befehl in einem Visual Studio-Eingabeaufforderungsfenster aus.

cl.exe /EHsc futures.cpp

Siehe auch

Exemplarische Vorgehensweisen für die Concurrency Runtime
Ausnahmebehandlung
task_group-Klasse
single_assignment-Klasse