Asynchrone Nachrichtenblöcke

Aktualisiert: August 2010

Die Agents Library stellt mehrere Nachrichtenblocktypen bereit, mit deren Hilfe Nachrichten threadsicher zwischen Anwendungskomponenten weitergegeben werden können. Diese Nachrichtenblocktypen werden häufig mit den verschiedenen Nachrichtenübergaberoutinen wie Concurrency::send, Concurrency::asend, Concurrency::receive und Concurrency::try_receive verwendet. Weitere Informationen zu den Nachrichtenübergaberoutinen, die von der Agents Library definiert werden, finden Sie unter Funktionen zum Übergeben von Meldungen.

Abschnitte

Dieses Thema enthält folgende Abschnitte:

  • Quellen und Ziele

  • Nachrichtenweitergabe

  • Übersicht über Nachrichtenblocktypen

  • unbounded_buffer-Klasse

  • overwrite_buffer-Klasse

  • single_assignment-Klasse

  • call-Klasse

  • transformer-Klasse

  • choice-Klasse

  • join- und multitype_join-Klasse

  • timer-Klasse

  • Nachrichtenfilterung

  • Nachrichtenreservierung

Quellen und Ziele

Quelle und Ziel sind zwei wichtige Beteiligte bei der Nachrichtenübergabe. Eine Quelle ist ein Kommunikationsendpunkt, der Nachrichten sendet. Ein Ziel ist ein Kommunikationsendpunkt, der Nachrichten empfängt. Sie können sich eine Quelle als Endpunkt vorstellen, von dem gelesen wird, und ein Ziel als Endpunkt, in den geschrieben wird. Anwendungen verbinden Quellen und Ziele, und bilden so Messagingnetzwerke.

Die Agents Library stellt Quellen und Ziele mithilfe von zwei abstrakten Klassen dar: Concurrency::ISource und Concurrency::ITarget. Nachrichtenblocktypen, die als Quelle dienen, werden von der ISource-Klasse abgeleitet, während Nachrichtenblocktypen, die als Ziel dienen, von der ITarget-Klasse abgeleitet werden. Nachrichtenblocktypen, die als Quelle und Ziel dienen, werden von der ISource-Klasse und der ITarget-Klasse abgeleitet.

[Nach oben]

Nachrichtenweitergabe

Bei der Nachrichtenweitergabe wird eine Nachricht von einer Komponente an eine andere gesendet. Wenn eine Nachricht für einen Nachrichtenblock bereitgestellt wird, kann er diese Nachricht akzeptieren, ablehnen oder verschieben. Jeder Nachrichtenblocktyp speichert und überträgt Nachrichten auf unterschiedliche Weise. Beispielsweise speichert die unbounded_buffer-Klasse eine unendliche Anzahl von Nachrichten, die overwrite_buffer-Klasse speichert jeweils nur eine Nachricht, und die Transformatorklasse speichert eine geänderte Version jeder Nachricht. Diese Nachrichtenblocktypen werden im Folgenden ausführlicher beschrieben.

Wenn ein Nachrichtenblock eine Meldung akzeptiert, können optionale Arbeiten durchgeführt werden, und wenn es sich bei dem Nachrichtenblock um eine Quelle handelt, kann die resultierende Nachricht an einen anderen Member im Netzwerk weitergegeben werden. Mithilfe einer Filterfunktion können Nachrichtenblöcke einzelne Nachrichten ablehnen, die nicht empfangen werden sollen. Filter werden später in diesem Thema im Abschnitt Nachrichtenfilterung ausführlicher beschrieben. Ein Nachrichtenblock, der eine Meldung verschiebt, kann sie reservieren und später verarbeiten. Die Nachrichtenreservierung wird im weiteren Verlauf dieses Themas im Abschnitt Nachrichtenreservierung ausführlicher beschrieben.

Mit der Agents Library können Nachrichten von Nachrichtenblöcken synchron oder asynchron übergeben werden. Wenn Sie eine Nachricht synchron an einen Nachrichtenblock übergeben, z. B. mit der send-Funktion, wird der aktuelle Kontext von der Laufzeit blockiert, bis die Nachricht vom Zielblock akzeptiert oder abgelehnt wird. Wenn Sie eine Nachricht asynchron an einen Nachrichtenblock übergeben, z. B. mit der asend-Funktion, wird die Nachricht von der Laufzeit für das Ziel bereitgestellt, und wenn die Nachricht vom Ziel akzeptiert wird, wird von der Laufzeit eine asynchrone Aufgabe geplant, um die Nachricht an den Empfänger weiterzugeben. Die Laufzeit verwendet einfache Aufgaben, um Nachrichten auf kooperative Weise weiterzugeben. Weitere Informationen zu einfachen Aufgaben finden Sie unter Taskplaner (Concurrency Runtime).

Anwendungen verbinden Quellen und Ziele, und bilden so Messagingnetzwerke. Normalerweise verknüpfen Sie das Netzwerk, und rufen send oder asend auf, um Daten an das Netzwerk zu übergeben. Um einen Quellnachrichtenblock mit einem Ziel zu verknüpfen, rufen Sie die Concurrency::ISource::link_target-Methode auf. Um die Verknüpfung eines Quellnachrichtenblocks mit einem Ziel aufzuheben, rufen Sie die Concurrency::ISource::link_target-Methode auf. Um die Verknüpfung eines Quellnachrichtenblocks mit allen Zielen aufzuheben, rufen Sie die Concurrency::ISource::unlink_targets-Methode auf. Wenn sich einer der vordefinierten Nachrichtenblocktypen nicht mehr im Bereich befindet oder zerstört wird, werden die Verknüpfungen mit den Zielblöcken automatisch aufgehoben. Bei einigen Nachrichtenblocktypen ist die maximale Anzahl der Ziele eingeschränkt, in die geschrieben werden kann. Im folgenden Abschnitt werden die Einschränkungen beschrieben, die für die vordefinierten Nachrichtenblocktypen gelten.

[Nach oben]

Übersicht über Nachrichtenblocktypen

In der folgenden Tabelle wird die Rolle der wichtigen Nachrichtenblocktypen kurz beschrieben.

  • unbounded_buffer
    Speichert eine Nachrichtenwarteschlange.

  • overwrite_buffer
    Speichert eine Nachricht, die mehrmals geschrieben und gelesen werden kann.

  • single_assignment
    Speichert eine Nachricht, die ein Mal geschrieben und gelesen werden kann.

  • call
    Führt beim Empfang einer Nachricht Arbeiten aus.

  • transformer
    Führt Arbeiten aus, wenn Daten empfangen werden, und sendet das Ergebnis dieser Arbeiten an einen anderen Zielblock. Die transformer-Klasse kann für unterschiedliche Eingabe- und Ausgabetypen verwendet werden.

  • choice
    Wählt aus einer Gruppe Quellen die erste verfügbare Nachricht aus.

  • join und multitype join
    Warten, bis alle Nachrichten, die von einer Gruppe Quellen empfangen werden sollen, empfangen wurden, und setzen die Nachrichten zu einer Nachricht für einen anderen Nachrichtenblock zusammen.

  • timer
    Sendet eine Nachricht in regelmäßigen Intervallen an einen Zielblock.

Diese Nachrichtenblocktypen haben unterschiedliche Eigenschaften, sodass sie für unterschiedliche Situationen nützlich sind. Zu diesen Eigenschaften zählen folgende:

  • Weitergabetyp: Gibt an, ob der Nachrichtenblock als Datenquelle, als Datenempfänger oder als Datenquelle und -empfänger dient.

  • Nachrichtenreihenfolge: Gibt an, ob der Nachrichtenblock die ursprüngliche Reihenfolge beibehält, in der Nachrichten gesendet oder empfangen wurden. Vordefinierte Nachrichtenblocktypen behalten die ursprüngliche Reihenfolge bei, in der Nachrichten gesendet oder empfangen werden.

  • Quellenanzahl: Die maximale Anzahl von Quellen, die der Nachrichtenblock lesen kann.

  • Zielanzahl: Die maximale Anzahl von Zielen, in die der Nachrichtenblock schreiben kann.

In der folgenden Tabelle wird der Bezug dieser Eigenschaften auf die verschiedenen Nachrichtenblocktypen beschrieben.

Nachrichtenblocktyp

Weitergabetyp (Quelle, Ziel oder beides)

Nachrichtenreihenfolge (sortiert oder nicht sortiert)

Quellenanzahl

Zielanzahl

unbounded_buffer

Beides

Testreihe

Unbegrenzt

Unbegrenzt

overwrite_buffer

Beides

Testreihe

Unbegrenzt

Unbegrenzt

single_assignment

Beides

Testreihe

Unbegrenzt

Unbegrenzt

call

Target

Testreihe

Unbegrenzt

Nicht zutreffend

transformer

Beides

Testreihe

Unbegrenzt

1

choice

Beides

Testreihe

10

1

join

Beides

Testreihe

Unbegrenzt

1

multitype_join

Beides

Testreihe

10

1

timer

Quelle

Nicht zutreffend

Nicht zutreffend

1

In den folgenden Abschnitten werden die Nachrichtenblocktypen ausführlicher beschrieben.

[Nach oben]

unbounded_buffer-Klasse

Die Concurrency::unbounded_buffer-Klasse stellt eine allgemeine Struktur des asynchronen Messaging dar. Diese Klasse speichert eine FIFO-Nachrichtenwarteschlange (First In, First Out), in die mehrere Quellen Nachrichten schreiben oder aus der mehrere Ziele Nachrichten auslesen können. Wenn ein Ziel eine Nachricht von einem unbounded_buffer-Objekt empfängt, wird diese Nachricht aus der Nachrichtenwarteschlange entfernt. Daher können die einzelnen Nachrichten nur von einem Ziel empfangen werden, obwohl ein unbounded_buffer-Objekt mehrere Ziele haben kann. Die unbounded_buffer-Klasse ist hilfreich, wenn Sie mehrere Nachrichten an eine andere Komponente übergeben möchten und diese Komponente alle Nachrichten empfangen muss.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der unbounded_buffer-Klasse veranschaulicht. In diesem Beispiel werden drei Werte an ein unbounded_buffer-Objekt gesendet und wieder von diesem Objekt zurückgegeben.

// unbounded_buffer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // Create an unbounded_buffer object that works with
   // int data.
   unbounded_buffer<int> items;

   // Send a few items to the unbounded_buffer object.
   send(items, 33);
   send(items, 44);
   send(items, 55);

   // Read the items from the unbounded_buffer object and print
   // them to the console.
   wcout << receive(items) << endl;
   wcout << receive(items) << endl;
   wcout << receive(items) << endl;
}

Dieses Beispiel erzeugt folgende Ausgabe:

33
44
55

Ein vollständiges Beispiel zur Verwendung der unbounded_buffer-Klasse finden Sie unter Gewusst wie: Implementieren verschiedener Producer-Consumer-Muster.

[Nach oben]

overwrite_buffer-Klasse

Die Concurrency::overwrite_buffer-Klasse ähnelt der unbounded_buffer-Klasse, wobei ein overwrite_buffer-Objekt nur eine Nachricht speichert. Wenn ein Ziel eine Nachricht von einem overwrite_buffer-Objekt empfängt, wird diese Nachricht darüber hinaus auch nicht aus dem Puffer entfernt. Daher empfangen mehrere Ziele eine Kopie der Nachricht.

Die overwrite_buffer-Klasse ist hilfreich, wenn Sie mehrere Nachrichten an eine andere Komponente übergeben möchten, diese Komponente jedoch nur den letzten Wert benötigt. Diese Klasse ist darüber hinaus auch hilfreich, wenn Sie eine Nachricht an mehreren Komponenten übertragen möchten.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der overwrite_buffer-Klasse veranschaulicht. In diesem Beispiel werden drei Werte an ein overwrite _buffer-Objekt gesendet, und der aktuelle Wert wird dreimal aus dem gleichen Objekt gelesen. Dieses Beispiel ähnelt dem Beispiel für die unbounded_buffer-Klasse. Allerdings speichert die overwrite_buffer-Klasse nur eine Nachricht. Darüber hinaus wird die Nachricht von der Laufzeit nicht aus einem overwrite_buffer-Objekt entfernt, nachdem sie gelesen wurde.

// overwrite_buffer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // Create an overwrite_buffer object that works with
   // int data.
   overwrite_buffer<int> item;

   // Send a few items to the overwrite_buffer object.
   send(item, 33);
   send(item, 44);
   send(item, 55);

   // Read the current item from the overwrite_buffer object and print
   // it to the console three times.
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
}

Dieses Beispiel erzeugt folgende Ausgabe:

55
55
55

Ein vollständiges Beispiel zur Verwendung der overwrite_buffer-Klasse finden Sie unter Gewusst wie: Implementieren verschiedener Producer-Consumer-Muster.

[Nach oben]

single_assignment-Klasse

Die Concurrency::single_assignment-Klasse gleicht der overwrite_buffer-Klasse, wobei hier jedoch nur einmal in ein single_assignment-Objekt geschrieben werden kann. Wenn ein Ziel eine Nachricht von einem single_assignment-Objekt empfängt, wird diese Nachricht wie bei der overwrite_buffer-Klasse nicht aus diesem Objekt entfernt. Daher empfangen mehrere Ziele eine Kopie der Nachricht. Die single_assignment-Klasse ist hilfreich, wenn Sie eine Nachricht an mehrere Komponenten übertragen möchten.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der single_assignment-Klasse veranschaulicht. In diesem Beispiel werden drei Werte an ein single_assignment-Objekt gesendet, und der aktuelle Wert wird dreimal aus dem gleichen Objekt gelesen. Dieses Beispiel ähnelt dem Beispiel für die overwrite_buffer-Klasse. Die overwrite_buffer-Klasse und die single_assignment-Klasse speichern jeweils eine einzelne Nachricht, in die single_assignment-Klasse kann jedoch nur einmal geschrieben werden.

// single_assignment-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // Create an single_assignment object that works with
   // int data.
   single_assignment<int> item;

   // Send a few items to the single_assignment object.
   send(item, 33);
   send(item, 44);
   send(item, 55);

   // Read the current item from the single_assignment object and print
   // it to the console three times.
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
}

Dieses Beispiel erzeugt folgende Ausgabe:

33
33
33

Ein vollständiges Beispiel zur Verwendung der single_assignment-Klasse finden Sie unter Exemplarische Vorgehensweise: Implementieren von Futures.

[Nach oben]

call-Klasse

Die Concurrency::call-Klasse dient als Nachrichtenempfänger, der beim Empfang von Daten eine Arbeitsfunktion ausführt. Bei dieser Arbeitsfunktion kann es sich um einen Lambda-Ausdruck, ein Funktionsobjekt oder einen Funktionszeiger handeln. Ein call-Objekt verhält sich anders als ein gewöhnlicher Funktionsaufruf, da es parallel zu anderen Komponenten agiert, die Nachrichten an das Objekt senden. Wenn ein call-Objekt beim Empfang einer Nachricht Arbeiten ausführt, fügt es die empfangene Nachricht einer Warteschlange hinzu. Jedes call-Objekt verarbeitet Nachrichten in der Warteschlange in der Reihenfolge, in der sie empfangen werden.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der call-Klasse veranschaulicht. In diesem Beispiel wird ein call-Objekt erstellt, das jeden empfangenen Wert an der Konsole ausgibt. Anschließend werden im Beispiel drei Werte an das call-Objekt gesendet. Da Nachrichten vom call-Objekt in einem separaten Thread verarbeitet werden, werden in diesem Beispiel auch eine Zählervariable sowie ein event-Objekt verwendet, um sicherzustellen, dass alle Nachrichten vom call-Objekt verarbeitet werden, bevor die wmain-Funktion zurückgegeben wird.

// call-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // An event that is set when the call object receives all values.
   event received_all;

   // Counts the 
   long receive_count = 0L;
   long max_receive_count = 3L;

   // Create an call object that works with int data.
   call<int> target([&received_all,&receive_count,max_receive_count](int n) {
      // Print the value that the call object receives to the console.
      wcout << n << endl;

      // Set the event when all messages have been processed.
      if (++receive_count == max_receive_count)
         received_all.set();
   });

   // Send a few items to the call object.
   send(target, 33);
   send(target, 44);
   send(target, 55);

   // Wait for the call object to process all items.
   received_all.wait();
}

Dieses Beispiel erzeugt folgende Ausgabe:

33
44
55

Ein vollständiges Beispiel zur Verwendung der call-Klasse finden Sie unter Gewusst wie: Bereitstellen von Arbeitsfunktionen für die call- und transformer-Klassen.

[Nach oben]

transformer-Klasse

Die Concurrency::transformer-Klasse dient sowohl als Nachrichtenempfänger als auch als Nachrichtensender. Die transformer-Klasse ist gleicht der call-Klasse, da sie beim Empfang von Daten eine benutzerdefinierte Arbeitsfunktion ausführt. Die transformer-Klasse sendet jedoch auch das Ergebnis der Arbeitsfunktion an Empfängerobjekte. Wie ein call-Objekt agiert ein transformer-Objekt parallel zu anderen Komponenten, die Nachrichten an das Objekt senden. Wenn ein transformer-Objekt beim Empfang einer Nachricht Arbeiten ausführt, fügt es die empfangene Nachricht einer Warteschlange hinzu. Jedes transformer-Objekt verarbeitet die eigenen Nachrichten in der Warteschlange in der Reihenfolge, in der sie empfangen werden.

Die transformer-Klasse sendet die eigene Nachricht an ein Ziel. Wenn Sie den _PTarget-Parameter im Konstruktor auf NULL festlegt haben, können Sie das Ziel später angeben, indem Sie die Concurrency::link_target-Methode aufrufen.

Im Gegensatz zu allen anderen asynchronen Nachrichtenblocktypen, die von der Agents Library bereitgestellt werden, kann die transformer-Klasse für unterschiedliche Eingabe- und Ausgabetypen verwendet werden. Aufgrund dieser Fähigkeit, Daten von einem Typ in einen anderen transformieren zu können, ist die transformer-Klasse in vielen parallelen Netzwerken eine wichtige Komponente. Zudem können Sie differenziertere Parallelitätsfunktionen in die Arbeitsfunktion eines transformer-Objekts integrieren.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der transformer-Klasse veranschaulicht. In diesem Beispiel wird ein transformer-Objekt erstellt, mit dem jeder int-Eingabewert mit 0.33 multipliziert wird, um einen double-Wert als Ausgabe zu generieren. Anschließend werden im Beispiel transformierten Werte vom selben transformer-Objekt empfangen und an der Konsole ausgegeben.

// transformer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // Create an transformer object that receives int data and 
   // sends double data.
   transformer<int, double> third([](int n) {
      // Return one-third of the input value.
      return n * 0.33;
   });

   // Send a few items to the transformer object.
   send(third, 33);
   send(third, 44);
   send(third, 55);

   // Read the processed items from the transformer object and print
   // them to the console.
   wcout << receive(third) << endl;
   wcout << receive(third) << endl;
   wcout << receive(third) << endl;
}

Dieses Beispiel erzeugt folgende Ausgabe:

10.89
14.52
18.15

Ein vollständiges Beispiel zur Verwendung der transformer-Klasse finden Sie unter Gewusst wie: Verwenden von transformer in einer Datenpipeline.

[Nach oben]

choice-Klasse

Die Concurrency::choice-Klasse wählt aus einer Gruppe Quellen die erste verfügbare Nachricht aus. Die choice-Klasse stellt keinen Datenflussmechanismus, sondern einen Ablaufsteuerungsmechanismus dar. (Im Thema Asynchronous Agents Library wird der Unterschied zwischen Datenfluss und Ablaufsteuerung beschrieben.)

Der Lesevorgang bei einem choice-Objekt gleicht dem Aufruf der API-Funktion WaitForMultipleObjects von Windows, wenn der bWaitAll-Parameter auf FALSE festgelegt ist. Die choice-Klasse bindet Daten jedoch nicht an ein externes Synchronisierungsobjekt, sondern an das Ereignis selbst.

In der Regel wird die choice-Klasse zusammen mit der Concurrency::receive-Funktion verwendet, um die Ablaufsteuerung in der Anwendung zu fördern. Verwenden Sie die choice-Klasse, wenn Sie unter Nachrichtenpuffern auswählen müssen, die andere Typen aufweisen. Verwenden Sie die single_assignment-Klasse, wenn Sie unter Nachrichtenpuffern auswählen müssen, die denselben Typ aufweisen.

Es ist wichtig, in welcher Reihenfolge Quellen mit einem choice-Objekt verknüpft werden, da die Reihenfolge bestimmen kann, welche Nachricht ausgewählt wird. Stellen Sie sich beispielsweise den Fall vor, dass mehrere Nachrichtenpuffer, die bereits eine Nachricht enthalten, mit einem choice-Objekt verknüpft werden. Das choice-Objekt wählt die Nachricht aus der ersten Quelle aus, mit der es verknüpft wird. Nach der Verknüpfung aller Quellen behält das choice-Objekt die Reihenfolge, in der die einzelnen Quellen eine Nachricht empfangen, bei.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der choice-Klasse veranschaulicht. In diesem Beispiel wird mit der Concurrency::make_choice-Funktion ein choice-Objekt erstellt, um aus den drei Nachrichtenblöcken auszuwählen. Anschließend werden im Beispiel verschiedene Fibonacci-Zahlen berechnet, und jedes Ergebnis wird in einem anderen Nachrichtenblock gespeichert. Im Beispiel wird dann in der Konsole eine Meldung angezeigt, die auf dem Vorgang basiert, der zuerst beendet wurde.

// choice-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
   if (n < 2)
      return n;
   return fibonacci(n-1) + fibonacci(n-2);
}

int wmain()
{
   // Although the following thee message blocks are written to one time only, 
   // this example illustrates the fact that the choice class works with 
   // different message block types.

   // Holds the 35th Fibonacci number.
   single_assignment<int> fib35;
   // Holds the 37th Fibonacci number.
   overwrite_buffer<int> fib37;
   // Holds half of the 42nd Fibonacci number.
   unbounded_buffer<double> half_of_fib42;   

   // Create a choice object that selects the first single_assignment 
   // object that receives a value.
   auto select_one = make_choice(&fib35, &fib37, &half_of_fib42);

   // Execute a few lengthy operations in parallel. Each operation sends its 
   // result to one of the single_assignment objects.
   parallel_invoke(
      [&fib35] { send(fib35, fibonacci(35)); },
      [&fib37] { send(fib37, fibonacci(37)); },
      [&half_of_fib42] { send(half_of_fib42, fibonacci(42) * 0.5); }
   );

   // Print a message that is based on the operation that finished first.
   switch (receive(select_one))
   {
   case 0:
      wcout << L"fib35 received its value first. Result = " 
            << receive(fib35) << endl;
      break;
   case 1:
      wcout << L"fib37 received its value first. Result = " 
            << receive(fib37) << endl;
      break;
   case 2:
      wcout << L"half_of_fib42 received its value first. Result = " 
            << receive(half_of_fib42) << endl;
      break;
   default:
      wcout << L"Unexpected." << endl;
      break;
   }
}

Dieses Beispiel erzeugt die folgende Beispielausgabe:

fib35 received its value first. Result = 9227465

Da die Aufgabe zur Berechnung der 35. Fibonacci-Zahl nicht unbedingt als erste abgeschlossen sein muss, kann die Ausgabe diese Beispiels variieren.

In diesem Beispiel werden die Fibonacci-Zahlen mithilfe des Concurrency::parallel_invoke-Algorithmus parallel berechnet. Weitere Informationen zu parallel_invoke finden Sie unter Parallele Algorithmen.

Ein vollständiges Beispiel zur Verwendung der choice-Klasse finden Sie unter Gewusst wie: Auswählen von abgeschlossenen Aufgaben.

[Nach oben]

join- und multitype_join-Klasse

Mit den Klassen Concurrency::join und Concurrency::multitype_join wird gewartet, bis jeder Member einer Gruppe von Quellen eine Nachricht empfangen hat. Die join-Klasse wird für Quellobjekte verwendet, die einen allgemeinen Nachrichtentyp aufweisen. Die multitype_join-Klasse wird für Quellobjekte verwendet, die andere Nachrichtentypen aufweisen können.

Der Lesevorgang bei einem join oder einem multitype_join-Objekt ähnelt dem Aufrufen der API-Funktion WaitForMultipleObjects von Windows, wenn der bWaitAll-Parameter auf TRUE festgelegt ist. Ähnlich wie ein choice-Objekt verwenden das join- und das multitype_join-Objekt einen Ereignismechanismus, der Daten nicht an ein externes Synchronisierungsobjekt, sondern an das Ereignis selbst bindet.

Beim Lesen aus einem join-Objekt wird ein std::vector-Objekt erzeugt. Beim Lesen aus einem multitype_join-Objekt wird ein std::tuple-Objekt erzeugt. Elemente treten in diesen Objekten in der Reihenfolge auf, in der die entsprechenden Quellpuffer mit dem join-Objekt oder mit dem multitype_join-Objekt verknüpft werden. Da die Reihenfolge, in der Quellpuffer mit einem join-Objekt oder einem multitype_join-Objekt verknüpft werden, von der Reihenfolge der Elemente im resultierenden vector-Objekt oder tuple-Objekt abhängig ist, wird empfohlen, die Verknüpfung zwischen einem vorhandenen Quellpuffer und einem Join nicht aufzuheben. Andernfalls ist das daraus folgende Verhalten möglicherweise undefiniert.

Gierige und nicht gierige Joins

Die join-Klasse und die multitype_join-Klasse unterstützen das Konzept gieriger und nicht gieriger Joins. Ein gieriger Join empfängt von allen zugehörigen Quellen eine Nachricht, wenn Nachrichten verfügbar werden, bis alle Nachrichten verfügbar sind. Ein nicht gieriger Join empfängt Nachrichten in zwei Phasen. Zunächst wartet ein nicht gieriger Join, bis eine Nachricht aus eine der Quelle bereitgestellt wird. Nachdem alle Quellmeldungen verfügbar sind, versucht ein nicht gieriger Join dann, diese Nachrichten zu reservieren. Wenn alle Nachrichten reserviert werden können, werden die einzelnen Nachrichten verarbeitet und an das Ziel weitergegeben. Wenn dies nicht der Fall ist, werden die Nachrichtenreservierungen freigegeben oder abgebrochen und es wird gewartet, bis die einzelnen Quellen eine Nachricht empfangen.

Gierige Joins erzielen im Vergleich zu nicht gierigen Joins eine bessere Leistung, da sie Nachrichten sofort empfangen. In seltenen Fällen können gierige Joins jedoch zu Deadlocks führen. Verwenden Sie einen nicht gierigen Join, wenn Sie mehrere Joins haben, die ein oder mehrere gemeinsame Quellobjekte enthalten.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der join-Klasse veranschaulicht. In diesem Beispiel wird mit der Concurrency::make_join-Funktion ein join-Objekt erstellt, das von drei single_assignment-Objekten empfängt. In diesem Beispiel werden verschiedene Fibonacci-Zahlen berechnet, das jeweilige Ergebnis wird in einem eigenen single_assignment-Objekt gespeichert, und die einzelnen Ergebnisse im join-Objekt werden an der Konsole ausgegeben. Dieses Beispiel ähnelt dem Beispiel für die choice-Klasse; im Unterschied dazu wartet die join-Klasse jedoch, bis alle Nachrichtenblöcke eine Nachricht empfangen haben.

// join-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
   if (n < 2)
      return n;
   return fibonacci(n-1) + fibonacci(n-2);
}

int wmain()
{
   // Holds the 35th Fibonacci number.
   single_assignment<int> fib35;
   // Holds the 37th Fibonacci number.
   single_assignment<int> fib37;
   // Holds half of the 42nd Fibonacci number.
   single_assignment<double> half_of_fib42;   

   // Create a join object that selects the values from each of the
   // single_assignment objects.
   auto join_all = make_join(&fib35, &fib37, &half_of_fib42);

   // Execute a few lengthy operations in parallel. Each operation sends its 
   // result to one of the single_assignment objects.
   parallel_invoke(
      [&fib35] { send(fib35, fibonacci(35)); },
      [&fib37] { send(fib37, fibonacci(37)); },
      [&half_of_fib42] { send(half_of_fib42, fibonacci(42) * 0.5); }
   );

   auto result = receive(join_all);
   wcout << L"fib35 = " << get<0>(result) << endl;
   wcout << L"fib37 = " << get<1>(result) << endl;
   wcout << L"half_of_fib42 = " << get<2>(result) << endl;
}

Dieses Beispiel erzeugt folgende Ausgabe:

fib35 = 9227465
fib37 = 24157817
half_of_fib42 = 1.33957e+008

In diesem Beispiel werden die Fibonacci-Zahlen mithilfe des Concurrency::parallel_invoke-Algorithmus parallel berechnet. Weitere Informationen zu parallel_invoke finden Sie unter Parallele Algorithmen.

Vollständige Beispiele für die Verwendung der join-Klasse finden Sie unter Gewusst wie: Auswählen von abgeschlossenen Aufgaben und Exemplarische Vorgehensweise: Verhindern von Deadlocks mit join.

[Nach oben]

timer-Klasse

Die Concurrency::timer-Klasse fungiert als Nachrichtenquelle. Ein timer-Objekt sendet nach Ablauf einer angegebenen Zeitdauer eine Nachricht an ein Ziel. Die timer-Klasse ist hilfreich, wenn der Versand einer Nachricht verzögert werden muss oder wenn eine Nachricht in regelmäßigen Intervallen gesendet werden muss.

Die timer-Klasse sendet die eigene Nachricht an nur ein Ziel. Wenn Sie den _PTarget-Parameter im Konstruktor auf NULL festlegt haben, können Sie das Ziel später angeben, indem Sie die Concurrency::ISource::link_target-Methode aufrufen.

Ein timer-Objekt kann ein sich wiederholendes oder ein sich nicht wiederholendes Objekt sein. Um ein sich wiederholendes timer-Objekt zu erstellen, übergeben Sie beim Aufruf des Konstruktors für den _Repeating-Parameter das true-Argument. Andernfalls übergeben Sie für den _Repeating-Parameter das false-Argument, um ein sich nicht wiederholendes timer-Objekt zu erstellen. Wenn das timer-Objekt ein sich wiederholendes Objekt ist, wird dieselbe Nachricht nach jedem Intervall an das entsprechende Ziel gesendet.

Die Agents Library erstellt timer-Objekte im nicht gestarteten Zustand. Rufen Sie die Concurrency::timer::start-Methode auf, um ein timer-Objekt zu starten. Zum Beenden eines timer-Objekts wird das Objekt zerstört oder die Concurrency::timer::stop-Methode aufgerufen. Rufen Sie die Concurrency::timer::pause-Methode auf, um einen sich wiederholenden Timer anzuhalten.

Beispiel

Im folgenden Beispiel wird die grundlegende Struktur für die Verwendung der timer-Klasse veranschaulicht. Im Beispiel wird mit dem timer-Objekt und dem call-Objekt der Status eines längeren Vorgangs angegeben.

// timer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
   if (n < 2)
      return n;
   return fibonacci(n-1) + fibonacci(n-2);
}

int wmain()
{
   // Create a call object that prints characters that it receives 
   // to the console.
   call<wchar_t> print_character([](wchar_t c) {
      wcout << c;
   });

   // Create a timer object that sends the period (.) character to 
   // the call object every 100 milliseconds.
   timer<wchar_t> progress_timer(100u, L'.', &print_character, true);

   // Start the timer.
   wcout << L"Computing fib(42)";
   progress_timer.start();

   // Compute the 42nd Fibonacci number.
   int fib42 = fibonacci(42);

   // Stop the timer and print the result.
   progress_timer.stop();
   wcout << endl << L"result is " << fib42 << endl;
}

Dieses Beispiel erzeugt die folgende Beispielausgabe:

Computing fib(42)..................................................
result is 267914296

Ein vollständiges Beispiel zur Verwendung der timer-Klasse finden Sie unter Gewusst wie: Senden einer Nachricht in regelmäßigen Intervallen.

[Nach oben]

Nachrichtenfilterung

Wenn Sie ein Nachrichtenblockobjekt erstellen, können Sie eine Filterfunktion angeben, die bestimmt, ob der Nachrichtenblock eine Meldung akzeptiert oder zurückweist. Eine Filterfunktion ist eine hilfreiche Möglichkeit, um sicherzustellen, dass nur bestimmte Werte von einem Nachrichtenblock empfangen werden.

Im folgenden Beispiel wird veranschaulicht, wie ein unbounded_buffer-Objekt erstellt wird, das eine Filterfunktion verwendet, um nur gerade Zahlen zu akzeptieren. Ungerade Zahlen werden vom unbounded_buffer-Objekt zurückgewiesen, sodass ungerade Zahlen nicht an die Zielblöcke weitergegeben werden.

// filter-function.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace Concurrency;
using namespace std;

int wmain()
{
   // Create an unbounded_buffer object that uses a filter
   // function to accept only even numbers.
   unbounded_buffer<int> accept_evens(
      [](int n) {
         return (n%2) == 0;
      });

   // Send a few values to the unbounded_buffer object.
   unsigned int accept_count = 0;
   for (int i = 0; i < 10; ++i)
   {
      // The asend function returns true only if the target
      // accepts the message. This enables us to determine
      // how many elements are stored in the unbounded_buffer
      // object.
      if (asend(accept_evens, i))
      {
         ++accept_count;
      }
   }

   // Print to the console each value that is stored in the 
   // unbounded_buffer object. The unbounded_buffer object should
   // contain only even numbers.
   while (accept_count > 0)
   {
      wcout << receive(accept_evens) << L' ';
      --accept_count;
   }
}

Dieses Beispiel erzeugt folgende Ausgabe:

0 2 4 6 8

Eine Filterfunktion kann eine Lambda-Funktion, ein Funktionsobjekt oder ein Funktionszeiger sein. Jede Filterfunktion nimmt eines der folgenden Formate an.

bool (_Type)
bool (_Type const &)

Um das unnötige Kopieren von Daten zu vermeiden, verwenden Sie das zweite Format bei einem aggregierten Typ, der anhand des Werts weitergegeben wird.

Die Nachrichtenfilterung unterstützt das Datenflussprogrammiermodell, in dem Berechnungen von Komponenten beim Empfang von Daten ausgeführt werden. Beispiele mit Filterfunktionen zum Steuern des Datenflusses in einem Nachrichtenübergabenetzwerk finden Sie unter Gewusst wie: Verwenden eines Nachrichtenblockfilters, Exemplarische Vorgehensweise: Erstellen eines Datenfluss-Agent und Exemplarische Vorgehensweise: Erstellen eines Bildverarbeitungsnetzwerks.

[Nach oben]

Nachrichtenreservierung

Mit der Nachrichtenreservierung können Nachrichten von einem Nachrichtenblock zur späteren Verwendung reserviert werden. In aller Regel wird die Nachrichtenreservierung nicht direkt verwendet. Kenntnisse der Nachrichtenreservierung helfen Ihnen jedoch möglicherweise, das Verhalten vordefinierter Nachrichtenblocktypen besser zu verstehen.

Beispiel: gierige und nicht gierige Joins. Beide Jointypen verwenden die Nachrichtenreservierung, um Meldungen für die spätere Verwendung zu reservieren. Wie bereits beschrieben, werden Nachrichten bei nicht gierigen Joins in zwei Phasen empfangen. In der ersten Phase wartet ein nicht gieriges join-Objekt, bis von den einzelnen Quellen eine Nachricht empfangen wurde. Ein nicht gieriger Join versucht anschließend, jede dieser Nachrichten zu reservieren. Wenn alle Nachrichten reserviert werden können, werden die einzelnen Nachrichten verarbeitet und an das Ziel weitergegeben. Wenn dies nicht der Fall ist, werden die Nachrichtenreservierungen freigegeben oder abgebrochen und es wird gewartet, bis die einzelnen Quellen eine Nachricht empfangen.

Bei einem gierigen Join, der auch Eingabenachrichten aus einer Reihe von Quellen liest, werden mithilfe der Nachrichtenreservierung weitere Nachrichten gelesen, während darauf gewartet wird, dass eine Nachricht von jeder Quelle empfangen wird. Angenommen, ein gieriger Join empfängt Nachrichten vom Nachrichtenblock A und vom Nachrichtenblock B. Wenn der gierige Join zwei Nachrichten von B empfängt, jedoch noch keine Nachrichten von A erhalten hat, wird die eindeutige Nachrichten-ID für die zweite Nachrichten von B vom gierigen Join gespeichert. Nachdem der gierige Join eine Meldung von A empfangen hat und diese Nachrichten weitergibt, wird anhand dieser Nachrichten-ID ermittelt, ob die zweite Nachricht von B weiterhin verfügbar ist.

Sie können die Nachrichtenreservierung verwenden, wenn Sie eigene Nachrichtenblocktypen implementieren. Ein Beispiel zum Erstellen eines eigenen Nachrichtenblocktyps finden Sie unter Exemplarische Vorgehensweise: Erstellen eines benutzerdefinierten Nachrichtenblocks.

[Nach oben]

Siehe auch

Konzepte

Asynchronous Agents Library

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

August 2010

Grundlegende Beispiele hinzugefügt.

Informationsergänzung.