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.

Eine Aufgabe ist eine Berechnung, die in weitere, differenziertere Berechnungen unterteilt werden kann. Ein Future ist eine asynchrone Aufgabe zur Berechnung eines Werts für die spätere Verwendung.

Zur Implementierung von Futures wird in diesem Thema die async_future-Klasse definiert. Die async_future-Klasse verwendet die folgenden 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 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:
    };
    
  2. 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;
    
  3. 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 asynchron im task_group-Datenmember aus und schreibt das Ergebnis dann mit der Concurrency::send-Funktion in den single_assignment-Datenmember.

    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());
        });
    }
    
  4. 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();
    }
    
  5. Implementieren Sie im public-Abschnitt der async_future-Klasse die get-Methode. Diese Methode ruft das Ergebnis der Arbeitsfunktion mit der Concurrency::receive-Funktion ab.

    // 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 Ganzzahlwerte 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(values.begin(), values.end(), [&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(values.begin(), values.end(), [&](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(values.begin(), values.end(), [&](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(values.begin(), values.end(), 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 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 zu erweitern, sodass sie Ausnahmen behandelt, die von der Arbeitsfunktion ausgelöst werden, ändern Sie die async_future::get-Methode, sodass die Concurrency::task_group::wait-Methode aufgerufen wird. 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, um das Ergebnis des async_future-Objekts bzw. den Wert der von der Arbeitsfunktion generierten Ausnahme auszugeben.

// 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 folgende Ausgabe:

caught exception: error

Weitere Informationen zum Ausnahmebehandlungsmodell in der Concurrency Runtime finden Sie unter Ausnahmebehandlung in der Concurrency Runtime.

Kompilieren des Codes

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

cl.exe /EHsc futures.cpp

Siehe auch

Referenz

task_group-Klasse

single_assignment-Klasse

Konzepte

Ausnahmebehandlung in der Concurrency Runtime

Weitere Ressourcen

Asynchrone Agents: Gewusst wie und exemplarische Vorgehensweisen