Freigeben über


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. Wir empfehlen die Verwendung von std::future oder concurrency::task , wenn Sie eine asynchrone Aufgabe benötigen, die einen Wert für die spätere Verwendung berechnet.

Eine Aufgabe ist eine Berechnung, die in weitere, feinere Berechnungen zerlegt werden kann. Ein future 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 Laufzeiten Parallelität: die Klasse concurrency::task_group 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 Datenelement task_group asynchron aus und schreibt das Ergebnis mit der Funktion concurrency::send in das single_assignment Datenelement.
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 , um das Ergebnis der Arbeitsfunktion abzurufen.
// 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); 
}

Example

Description

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 Klasse async_future so zu erweitern, dass sie von der Arbeitsfunktion ausgelöste Ausnahmen verarbeitet, ändern Sie die Methode async_future::get so, dass sie die Methode concurrency::task_group::wait aufruft. 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 verwendet einen try-catch -Block, um das Ergebnis des async_future -Objekts oder den Wert der Ausnahme auszugeben, 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 den Laufzeiten Parallelität finden Sie unter Ausnahmebehandlung.

Kompilieren des Codes

Kopieren Sie den Beispielcode, und fügen Sie ihn in ein Visual Studio-Projekt ein. Alternativ dazu können Sie ihn auch in eine Datei mit dem Namen futures.cpp einfügen und dann folgenden Befehl in einem Visual Studio-Eingabeaufforderungsfenster ausführen.

cl.exe /EHsc futures.cpp

Siehe auch

Leitfäden zur Parallelität von Laufzeiten
Ausnahmebehandlung
task_group Klasse
single_assignment Klasse