Partager via


Blocs de messages asynchrones

La bibliothèque d'agents fournit plusieurs types de blocs de messages qui vous permettent de propager des messages sur des composants d'application de manière thread-safe. Ces types de blocs de messages sont souvent utilisés avec les différentes routines de passage de messages, telles que concurrency::send, concurrency::asend, concurrency::receive et concurrency::try_receive. Pour plus d'informations sur les routines de passage de messages définies par la Bibliothèque d'agents, consultez Fonctions de passage de messages.

Sections

Cette rubrique contient les sections suivantes :

  • Sources et cibles

  • Propagation de messages

  • Vue d'ensemble des types de blocs de messages

  • Classe unbounded_buffer

  • Classe overwrite_buffer

  • Classe single_assignment

  • Classe call

  • Classe transformer

  • Classe choice

  • Classes join et multitype_join

  • Classe timer

  • Filtrage de messages

  • Réservation de messages

Sources et cibles

Les sources et les cibles sont deux participants importants au passage de message. Une source fait référence à un point de terminaison de communication qui envoie des messages. Une cible fait référence à un point de terminaison de communication qui reçoit des messages. On peut considérer une source comme un point de terminaison à partir duquel on lit et une cible comme un point de terminaison dans lequel on écrit. Les applications connectent les sources et les cibles pour former des réseaux de messagerie.

La bibliothèque d'agents utilise deux classes abstraites pour représenter les sources et les cibles : concurrency::ISource et concurrency::ITarget. Les types de blocs de messages qui jouent le rôle de sources dérivent d'ISource ; les types de blocs de messages qui jouent le rôle de cibles dérivent d' ITarget. Les types de blocs de messages qui jouent le rôle de sources et de cibles dérivent de ISource et de ITarget.

[Premières]

Propagation de messages

La propagation de message est l'acte qui consiste à envoyer un message d'un composant à un autre. Lorsqu'un bloc de message reçoit un message, il peut l'accepter, le refuser ou le reporter. Chaque bloc de message stocke et transmet des messages de différentes façons. Par exemple, la classe unbounded_buffer stocke un nombre de messages illimité, la classe overwrite_buffer enregistre un seul message à la fois et la classe transformer stocke une version modifiée de chaque message. Ces types de blocs de messages sont décrits plus en détail ultérieurement dans ce document.

Lorsqu'un bloc de message reçoit un message, il peut éventuellement effectuer le travail et, si le bloc de message est une source, passer le message résultant à un autre membre du réseau. Un bloc de message peut utiliser une fonction de filtre pour refuser les messages qu'il ne souhaite pas recevoir. Les filtres sont décrits plus en détail ultérieurement dans cette rubrique, dans la section Filtrage de messages. Un bloc de message qui reporte un message peut réserver ce message et l'utiliser ultérieurement. La réservation de messages est décrite plus en détail ultérieurement dans cette rubrique, dans la section Réservation de messages.

La bibliothèque d'agents permet aux blocs de messages de passer des messages de façon asynchrone ou synchrone. Lorsque vous passez un message à un bloc de message de façon synchrone, par exemple à l'aide de la fonction send, le runtime bloque le contexte actuel jusqu'à ce que le bloc cible accepte ou rejette le message. Lorsque vous passez un message à un bloc de message asynchrone, par exemple à l'aide de la fonction asend, le runtime envoie un message à la cible. Si la cible l'accepte, le runtime planifie une tâche asynchrone qui propage le message au récepteur. Le runtime utilise des tâches légères pour propager des messages de manière coopérative. Pour plus d'informations sur les tâches légères, consultez Planificateur de tâches (runtime d'accès concurrentiel).

Les applications connectent les sources et les cibles pour former des réseaux de messagerie. En général, vous associez le réseau et appelez send ou asend pour passer des données au réseau. Pour connecter un bloc de message source vers une cible, appelez la méthode concurrency::ISource::link_target. Pour déconnecter un bloc source d'une cible, appelez la méthode concurrency::ISource::unlink_target. Pour déconnecter un bloc source de toutes ses cibles, appelez la méthode concurrency::ISource::unlink_targets. Lorsque l'un des types de bloc de message prédéfinis quitte la portée ou qu'il est détruit, il se déconnecte automatiquement de tous les blocs cibles. Certains types de blocs de messages limitent le nombre maximal de cibles auxquelles ils peuvent écrire. La section suivante décrit les restrictions qui s'appliquent aux types de bloc de message prédéfinis.

[Premières]

Vue d'ensemble des types de blocs de messages

Le tableau suivant décrit brièvement le rôle des types de blocs de messages importants.

  • unbounded_buffer
    Stocke une file d'attente de messages.

  • overwrite_buffer
    Stocke un message qui peut être sujet à plusieurs lectures et écritures.

  • single_assignment
    Stocke un message qui peut être sujet à une seule écriture et à plusieurs lectures.

  • appel
    Exécute le travail en cas de réception d'un message.

  • transformer
    Exécute le travail en cas de réception de données et envoie le résultat de ce travail à un autre bloc cible. La classe transformer peut agir sur différents types d'entrées et de sorties.

  • choice
    Sélectionne le premier message disponible d'un jeu de sources.

  • join et multitype join
    Attendent que tous les messages soient reçus en provenance d'un jeu de sources, puis combinent les messages dans un message unique pour un autre bloc de message.

  • timer
    Envoie un message à un bloc cible à intervalle régulier.

Ces types de blocs de messages ont des caractéristiques différentes qui les rendent utiles pour différentes situations. Voici certaines de ces caractéristiques :

  • Type de propagation : indique si le bloc de message joue le rôle de source de données, de récepteur de données ou les deux.

  • Ordonnancement des messages : indique si le bloc de message conserve l'ordre d'origine dans lequel les messages sont envoyés ou reçus. Chaque type de bloc de message prédéfini conserve l'ordre d'origine dans lequel il envoie ou reçoit des messages.

  • Nombre de sources : nombre maximal de sources à partir desquelles le bloc de message peut lire.

  • Nombre de cibles : nombre maximal de cibles dans lesquelles le bloc de message peut écrire.

Le tableau suivant indique la relation entre ces caractéristiques et différents types de blocs de messages.

Type de bloc de message

Type de propagation (Source, Cible ou Les deux)

Ordonnancement des messages (Ordonné ou Non ordonné)

Nombre de sources

Nombre de cibles

unbounded_buffer

Les deux

Ordonné

Illimité

Illimité

overwrite_buffer

Les deux

Ordonné

Illimité

Illimité

single_assignment

Les deux

Ordonné

Illimité

Illimité

call

Cible

Ordonné

Illimité

Non applicable

transformer

Les deux

Ordonné

Illimité

1

choice

Les deux

Ordonné

10

1

join

Les deux

Ordonné

Illimité

1

multitype_join

Les deux

Ordonné

10

1

timer

Source

Non applicable

Non applicable

1

Les sections suivantes décrivent les types de blocs de messages de façon approfondie.

[Premières]

Classe unbounded_buffer

La classe concurrency::unbounded_buffer représente une structure de messagerie asynchrone à caractère général. Cette classe stocke une file d'attente de messages « premier entré, premier sorti » (FIFO) accessible en écriture ou en lecture par plusieurs sources ou cibles. Lorsqu'une cible reçoit un message d'un objet unbounded_buffer, ce message est supprimé de la file d'attente de messages. Par conséquent, bien qu'un objet unbounded_buffer puisse avoir plusieurs cibles, une seule cible reçoit chaque message. La classe unbounded_buffer est utile lorsque vous voulez passer plusieurs messages à un autre composant et que ce composant doit recevoir chaque message.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe unbounded_buffer. Cet exemple envoie trois valeurs à un objet unbounded_buffer, puis relit ces valeurs à partir du même objet.

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

Cet exemple génère la sortie suivante :

  

Pour obtenir un exemple complet qui indique comment utiliser la classe unbounded_buffer, consultez Comment : implémenter divers modèles de producteur-consommateur.

[Premières]

Classe overwrite_buffer

La classe concurrency::overwrite_buffer ressemble à la classe unbounded_buffer, hormis le fait qu'un objet overwrite_buffer ne stocke qu'un seul message. De plus, lorsqu'une cible reçoit un message d'un objet overwrite_buffer, ce message n'est pas supprimé de la mémoire tampon. Par conséquent, plusieurs cibles reçoivent une copie du message.

La classe overwrite_buffer est utile lorsque vous voulez passer plusieurs messages à un autre composant, mais que ce composant nécessite uniquement la valeur la plus récente. Cette classe est également utile lorsque vous voulez diffuser un message à plusieurs composants.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe overwrite_buffer. Cet exemple envoie trois valeurs à un objet overwrite _buffer, puis relit trois fois la valeur actuelle à partir du même objet. Cet exemple est similaire à l'exemple de la classe unbounded_buffer. Toutefois, la classe overwrite_buffer stocke seulement un message. En outre, le runtime ne supprime pas le message d'un objet overwrite_buffer une fois qu'il est lu.

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

Cet exemple génère la sortie suivante :

  

Pour obtenir un exemple complet qui indique comment utiliser la classe overwrite_buffer, consultez Comment : implémenter divers modèles de producteur-consommateur.

[Premières]

Classe single_assignment

La classe concurrency::single_assignment est semblable à la classe overwrite_buffer, hormis le fait qu'un objet single_assignment est accessible une seule fois en écriture. Comme pour la classe overwrite_buffer, lorsqu'une cible reçoit un message d'un objet single_assignment, ce message n'est pas supprimé de cet objet. Par conséquent, plusieurs cibles reçoivent une copie du message. La classe single_assignment est utile lorsque vous voulez diffuser un message à plusieurs composants.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe single_assignment. Cet exemple envoie trois valeurs à un objet single_assignment, puis relit trois fois la valeur actuelle à partir du même objet. Cet exemple est similaire à l'exemple de la classe overwrite_buffer. Bien que les classes overwrite_buffer et single_assignment stockent un seul message, la classe single_assignment est accessible une seule fois en écriture.

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

Cet exemple génère la sortie suivante :

  

Pour obtenir un exemple complet qui indique comment utiliser la classe single_assignment, consultez Procédure pas à pas : implémentation de tâches futures.

[Premières]

Classe call

La classe concurrency::call joue le rôle de récepteur de message qui exécute une fonction de travail en cas de réception de données. Cette fonction de travail peut être une expression lambda, un objet de fonction ou un pointeur fonction. Un objet call se comporte différemment d'un appel de fonction ordinaire car il agit en parallèle aux autres composants qui lui envoient des messages. Si un objet call exécute un travail lorsqu'il reçoit un message, il ajoute ce message à une file d'attente. Chaque objet call traite les messages en file d'attente dans l'ordre dans lequel ils sont reçus.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe call. Cet exemple crée un objet call qui imprime chaque valeur qu'il reçoit dans la console. L'exemple envoie ensuite trois valeurs à l'objet call. Étant donné que l'objet call traite les messages dans un thread séparé, cet exemple utilise également une variable de compteur et un objet event pour vérifier que l'objet call traite tous les messages avant que la fonction wmain retourne une valeur.

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

Cet exemple génère la sortie suivante :

  

Pour obtenir un exemple complet qui indique comment utiliser la classe call, consultez Comment : fournir des fonctions de travail aux classes call et transformer.

[Premières]

Classe transformer

La classe concurrency::transformer agit à la fois comme un récepteur de message et un expéditeur de message. La classe transformer ressemble à la classe call, car elle exécute une fonction de travail définie par l'utilisateur lorsqu'elle reçoit des données. Toutefois, la classe transformer envoie également le résultat de la fonction de travail à des objets récepteurs. Comme un objet call, un objet transformer agit en parallèle à d'autres composants qui lui envoient des messages. Si un objet transformer exécute un travail lorsqu'il reçoit un message, il ajoute ce message à une file d'attente. Chaque objet transformer traite ses messages en file d'attente dans l'ordre dans lequel ils sont reçus.

La classe transformer envoie son message à une cible. Si vous affectez au paramètre _PTarget dans le constructeur la valeur NULL, vous pouvez spécifier ultérieurement la cible en appelant la méthode concurrency::link_target.

Contrairement à tous les autres types de blocs de messages asynchrones fournis par la Bibliothèque d'agents, la classe transformer peut agir sur différents types d'entrées et de sorties. Cette capacité à transformer des données d'un type en un autre fait de la classe transformer un composant clé dans de nombreux réseaux simultanés. De plus, vous pouvez ajouter d'autres fonctionnalités parallèles affinées dans la fonction de travail d'un objet transformer.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe transformer. Cet exemple crée un objet transformer qui multiplie chaque valeur d'entrée int par 0,33 afin de produire une valeur double en sortie. L'exemple accepte ensuite les valeurs transformées du même objet transformer et les imprime sur la console.

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

Cet exemple génère la sortie suivante :

  

Pour obtenir un exemple complet qui indique comment utiliser la classe transformer, consultez Comment : utiliser la classe transformer dans un pipeline de données.

[Premières]

Classe choice

La classe concurrency::choice sélectionne le premier message disponible dans un jeu de sources. La classe choice représente un mécanisme de flux de contrôle plutôt qu'un mécanisme de flux de données (la rubrique Bibliothèque d'agents asynchrones décrit les différences entre les flux de données et les flux de contrôle).

La lecture d'un objet choice s'apparente à l'appel de la fonction WaitForMultipleObjects de l'API Windows lorsque le paramètre bWaitAll a la valeur FALSE. Toutefois, la classe choice lie les données à l'événement lui-même plutôt qu'à un objet de synchronisation externe.

En général, on utilise la classe choice avec la fonction concurrency::receive pour piloter le flux de contrôle dans l'application. Utilisez la classe choice lorsque vous devez sélectionner parmi des tampons de messages qui ont des types différents. Utilisez la classe single_assignment lorsque vous devez sélectionner parmi des tampons de messages qui ont le même type.

L'ordre dans lequel vous liez les sources à un objet choice est important car il peut déterminer quel message est sélectionné. Par exemple, considérez le cas dans lequel vous liez plusieurs tampons de messages qui contiennent déjà un message à un objet choice. L'objet choice sélectionne le message à partir de la première source à laquelle il est lié. Une fois que vous avez lié toutes les sources, l'objet choice conserve l'ordre dans lequel chaque source reçoit un message.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe choice. Cet exemple utilise la fonction concurrency::make_choice pour créer un objet choice qui effectue une sélection parmi les trois blocs de messages. L'exemple calcule ensuite plusieurs nombres de Fibonacci et stocke chaque résultat dans un bloc de message distinct. L'exemple imprime ensuite sur la console un message basé sur l'opération qui s'est terminée en premier.

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

Cet exemple génère l'exemple de sortie suivant :

       

Étant donné qu'il n'est pas garanti que la tâche qui calcule le 35ème nombre de Fibonacci se termine en premier, le résultat de cet exemple peut varier.

Cet exemple utilise l'algorithme concurrency::parallel_invoke pour calculer les nombres de Fibonacci en parallèle. Pour plus d'informations sur parallel_invoke, consultez Algorithmes parallèles.

Pour obtenir un exemple complet qui indique comment utiliser la classe choice, consultez Comment : effectuer une sélection parmi les tâches terminées.

[Premières]

Classes join et multitype_join

Les classes concurrency::join et concurrency::multitype_join vous permettent d'attendre que chaque membre d'un jeu de sources reçoive un message. La classe join agit sur les objets sources qui ont un type de message commun. La classe multitype_join agit sur les objets source qui peuvent avoir des types de messages différents.

La lecture d'un objet join ou multitype_join s'apparente à l'appel de la fonction WaitForMultipleObjects de l'API Windows lorsque le paramètre bWaitAll a la valeur TRUE. Cependant, tout comme un objet choice, les objets join et multitype_join utilisent un mécanisme d'événement qui lie les données à l'événement lui-même plutôt qu'à un objet de synchronisation externe.

La lecture à partir d'un objet join génère un objet std::vector. La lecture à partir d'un objet multitype_join génère un objet std::tuple. Les éléments apparaissent dans ces objets dans le même ordre que celui dans lequel leurs mémoires tampons sources correspondantes sont liées à l'objet join ou multitype_join. Étant donné que l'ordre dans lequel vous associez des mémoires tampons sources à un objet join ou multitype_join dépend de l'ordre des éléments de l'objet vector ou tuple qui en résulte, nous vous conseillons de ne pas dissocier une mémoire tampon source d'une jointure. Cela pourrait entraîner un comportement non spécifié.

Jointures gourmandes et non gourmandes

Les classes multitype_join et join prennent en charge le concept de jointures gourmandes et non gourmandes. Une jointure gourmande accepte un message de chacune de ses sources à mesure que les messages deviennent disponibles jusqu'à ce que tous les messages soient disponibles. Une jointure non gourmande reçoit les messages en deux phases. Tout d'abord, une jointure non gourmande attend jusqu'à la réception d'un message de chacune de ses sources. Ensuite, lorsque tous les messages sources sont disponibles, une jointure non gourmande tente de réserver chacun de ces messages. Si chaque message peut être réservé, elle consomme tous les messages et les propage à sa cible. Dans le cas contraire, elle libère ou elle annule les réservations de messages et attend à nouveau que chaque source reçoive un message.

Les jointures gourmandes sont plus performantes que les jointures non gourmandes car elles acceptent les messages immédiatement. Toutefois, dans de rares cas, les jointures gourmandes peuvent provoquer des interblocages. Utilisez une jointure non gourmande lorsque vous avez plusieurs jointures qui contiennent un ou plusieurs objets sources partagés.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe join. Cet exemple utilise la fonction concurrency::make_join pour créer un objet join qui reçoit de trois objets single_assignment. Cet exemple calcule plusieurs nombres de Fibonacci, stocke chaque résultat dans un objet différent single_assignment, puis imprime sur la console chaque résultat que l'objet join contient. Cet exemple est semblable à l'exemple de la classe choice, hormis le fait que la classe join attend que tous les blocs de messages sources aient reçu un message.

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

Cet exemple génère la sortie suivante :

  

Cet exemple utilise l'algorithme concurrency::parallel_invoke pour calculer les nombres de Fibonacci en parallèle. Pour plus d'informations sur parallel_invoke, consultez Algorithmes parallèles.

Pour obtenir des exemples complets illustrant l'utilisation de la classe join, consultez Comment : effectuer une sélection parmi les tâches terminées et Procédure pas à pas : utilisation de la classe join pour empêcher l'interblocage.

[Premières]

Classe timer

La classe concurrency::timer joue le rôle de source de message. Un objet timer envoie un message à une cible après l'expiration d'un certain délai. La classe timer est utile lorsque vous devez différer l'envoi d'un message ou que vous voulez envoyer un message à intervalle régulier.

La classe timer envoie son message à une seule cible. Si vous affectez au paramètre _PTarget dans le constructeur la valeur NULL, vous pouvez spécifier ultérieurement la cible en appelant la méthode concurrency::ISource::link_target.

Un objet timer peut répétitif ou non répétitif. Pour créer une minuterie répétitive, passez true pour le paramètre _Repeating lorsque vous appelez le constructeur. Sinon, passez false pour le paramètre _Repeating pour créer une minuterie non répétitive. Si la minuterie est répétitive, elle envoie le même message à sa cible après chaque intervalle.

La Bibliothèque d'agents crée les objets timer à l'état non démarré. Pour démarrer un objet timer, appelez la méthode concurrency::timer::start. Pour arrêter un objet timer, détruisez l'objet ou appelez la méthode concurrency::timer::stop. Pour suspendre une minuterie répétitive, appelez la méthode concurrency::timer::pause.

Exemple

L'exemple suivant illustre la structure de base pour savoir utiliser la classe timer. L'exemple utilise des objets timer et call pour indiquer la progression d'une longue opération.

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

Cet exemple génère l'exemple de sortie suivant :

  

Pour obtenir un exemple complet qui indique comment utiliser la classe timer, consultez Comment : envoyer un message à intervalles réguliers.

[Premières]

Filtrage de messages

Lorsque vous créez un objet de bloc de message, vous pouvez fournir une fonction de filtre qui détermine si le bloc de message accepte ou rejette un message. Une fonction de filtre est un moyen utile pour garantir qu'un bloc de message ne reçoit que certaines valeurs.

L'exemple suivant indique comment créer un objet unbounded_buffer qui utilise une fonction de filtre pour accepter uniquement des chiffres pairs. L'objet unbounded_buffer rejette les nombres impairs. Par conséquent, il ne propage pas les nombres impairs à ses blocs cibles.

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

Cet exemple génère la sortie suivante :

  

Une fonction de filtre peut être une fonction lambda, un pointeur fonction ou un objet de fonction. Chaque fonction de filtre se présente sous l'une des formes suivantes.

  

Pour éviter la copie inutile des données, utilisez la deuxième forme lorsque le type d'agrégat que vous utilisez est propagé par valeur.

Le filtrage de message prend en charge le modèle de programmation du flux de données, dans lequel les composants exécutent des calculs lorsqu'ils reçoivent des données. Pour obtenir des exemples qui utilisent des fonctions de filtre pour contrôler le flux de données dans un réseau de passage de message, consultez Comment : utiliser un filtre de bloc de message, Procédure pas à pas : création des agents de flux de données et Procédure pas à pas : création d'un réseau de traitement d'image.

[Premières]

Réservation de messages

La réservation de messages permet à un bloc de message de réserver un message pour l'utiliser ultérieurement. En général, la réservation de messages n'est pas utilisée directement. Toutefois, comprendre la réservation de messages peut vous aider à mieux comprendre le comportement de certains types de bloc de message prédéfinis.

Prenez en considération les jointures gourmandes et non gourmandes. Les deux types de jointures utilisent la réservation de messages pour réserver des messages pour une utilisation ultérieure. Comme indiqué précédemment, une jointure non gourmande reçoit des messages en deux phases. Lors de la première phase, un objet join de jointure non gourmande attend que chaque source reçoive un message. Une jointure non gourmande tente alors de réserver chacun de ces messages. Si chaque message peut être réservé, elle consomme tous les messages et les propage à sa cible. Dans le cas contraire, elle libère ou elle annule les réservations de messages et attend à nouveau que chaque source reçoive un message.

Une jointure gourmande, qui lit également les messages d'entrée provenant de plusieurs sources, utilise la réservation de messages pour lire les messages supplémentaires tout en attendant de recevoir un message de chaque source. Prenons l'exemple d'une jointure gourmande qui reçoit des messages des blocs de message A et B. Si la jointure gourmande reçoit deux messages de B, mais qu'elle n'a pas encore reçu un message de A, la jointure gourmande enregistre l'identificateur unique de message du deuxième message provenant de B. Lorsque la jointure gourmande reçoit un message de A et qu'elle propage ces messages, elle utilise l'identificateur de message stocké pour voir si le deuxième message de B est toujours disponible.

Vous pouvez utiliser la réservation de messages lorsque vous implémentez vos propres types de bloc de message personnalisés. Pour obtenir un exemple illustrant la création d'un type de bloc de message personnalisé, consultez Procédure pas à pas : création d'un bloc de message personnalisé.

[Premières]

Voir aussi

Concepts

Bibliothèque d'agents asynchrones