Procédure pas à pas : création d'une application basée sur un agent
Cette rubrique décrit comment créer une application de base basée sur agent. Dans cette procédure pas à pas, vous pouvez créer un agent qui lit de façon asynchrone des données à partir d'un fichier texte. L'application utilise l'algorithme de somme de contrôle Adler-32 pour calculer la somme de contrôle du contenu de ce fichier.
Composants requis
Vous devez comprendre les sujets suivants afin d'effectuer cette procédure pas-à-pas :
Sections
Cette procédure pas-à-pas explique comment effectuer les tâches suivantes :
Création de l'application console
Création de la classe file_reader
Utilisation de la classe file_reader dans l'application
Création de l'application console
Cette section indique comment créer une application console Visual C++ qui fait référence aux fichiers d'en-tête que le programme utilisera.
Pour créer une application Visual C++ à l'aide de l'Assistant Application console Win32
Dans le menu Fichier, cliquez sur Nouveau, puis sur Projet pour afficher la boîte de dialogue Nouveau projet.
Dans la boîte de dialogue Nouveau projet, sélectionnez le nœud Visual C++ dans le volet Types de projets, puis sélectionnez Application console Win32 dans le volet Modèles. Tapez un nom pour le projet, par exemple AgentDeBase, puis cliquez sur OK pour afficher l'Assistant Application console Win32.
Dans la boîte de dialogue Assistant Application console Win32, cliquez sur Terminer.
Dans stdafx.h, ajoutez le code suivant :
#include <agents.h> #include <string> #include <iostream> #include <algorithm>
Le fichier d'en-tête agents.h contient la fonctionnalité de la classe concurrency::agent.
Vérifiez que l'application a été créée avec succès en la générant et en l'exécutant. Pour générer l'application, dans le menu Générer, cliquez sur Générer la solution. Si l'application est générée avec succès, exécutez-la en cliquant sur le bouton Démarrer le débogage dans le menu Déboguer.
[Premières]
Création de la classe file_reader
Cette section indique comment créer la classe file_reader. Le runtime planifie chaque agent pour effectuer le travail dans son propre contexte. Par conséquent, vous pouvez créer un agent qui effectue le travail de façon synchrone, mais qui interagit de façon asynchrone avec d'autres composants. La classe file_reader lit des données à partir d'un fichier d'entrée donné et envoie des données de ce fichier vers un composant cible donné.
Pour créer la classe file_reader
Ajoutez un nouveau fichier d'en-tête C++ à votre projet. Pour cela, cliquez avec le bouton droit sur le nœud Fichiers d'en-tête dans l'Explorateur de solutions, cliquez sur Ajouter, puis sur Nouvel élément. Dans le volet Modèles, sélectionnez Fichier d'en-tête (.h). Dans la boîte de dialogue Ajouter un nouvel élément, tapez file_reader.h dans la zone Nom, puis cliquez sur Ajouter.
Dans file_reader.h, ajoutez le code suivant.
#pragma once
Dans file_reader.h, créez une classe nommée file_reader qui dérive d'agent.
class file_reader : public concurrency::agent { public: protected: private: };
Ajoutez les membres de données suivants à la section private de votre classe.
std::string _file_name; concurrency::ITarget<std::string>& _target; concurrency::overwrite_buffer<std::exception> _error;
Le membre _file_name est le nom de fichier à partir duquel l'agent lit. Le membre _target est un objet concurrency::ITarget dans lequel l'agent écrit le contenu du fichier. Le membre _error contient toute erreur qui se produit pendant la vie de l'agent.
Ajoutez le code suivant pour les constructeurs file_reader à la section public de la classe file_reader.
explicit file_reader(const std::string& file_name, concurrency::ITarget<std::string>& target) : _file_name(file_name) , _target(target) { } explicit file_reader(const std::string& file_name, concurrency::ITarget<std::string>& target, concurrency::Scheduler& scheduler) : agent(scheduler) , _file_name(file_name) , _target(target) { } explicit file_reader(const std::string& file_name, concurrency::ITarget<std::string>& target, concurrency::ScheduleGroup& group) : agent(group) , _file_name(file_name) , _target(target) { }
Chaque surcharge de constructeur définit les membres de données file_reader. La surcharge des deuxième et troisième constructeurs permet à votre application d'utiliser un planificateur spécifique avec votre agent. La première surcharge utilise le planificateur par défaut avec votre agent.
Ajoutez la méthode get_error à la section public de la classe file_reader.
bool get_error(std::exception& e) { return try_receive(_error, e); }
La méthode get_error extrait toute erreur qui se produit pendant la vie de l'agent.
Implémentez la méthode concurrency::agent::run dans la section protected de votre classe.
void run() { FILE* stream; try { // Open the file. if (fopen_s(&stream, _file_name.c_str(), "r") != 0) { // Throw an exception if an error occurs. throw std::exception("Failed to open input file."); } // Create a buffer to hold file data. char buf[1024]; // Set the buffer size. setvbuf(stream, buf, _IOFBF, sizeof buf); // Read the contents of the file and send the contents // to the target. while (fgets(buf, sizeof buf, stream)) { asend(_target, std::string(buf)); } // Send the empty string to the target to indicate the end of processing. asend(_target, std::string("")); // Close the file. fclose(stream); } catch (const std::exception& e) { // Send the empty string to the target to indicate the end of processing. asend(_target, std::string("")); // Write the exception to the error buffer. send(_error, e); } // Set the status of the agent to agent_done. done(); }
La méthode run ouvre le fichier et lit les données qu'il contient. La méthode run utilise la gestion des exceptions pour capturer les erreurs qui se produisent pendant le traitement de fichier.
Chaque fois que cette méthode lit des données à partir du fichier, elle appelle la fonction concurrency::asend pour envoyer ces données à la mémoire tampon cible. Elle envoie la chaîne vide à sa mémoire tampon cible pour indiquer la fin du traitement.
L'exemple suivant affiche le contenu complet de file_reader.h.
#pragma once
class file_reader : public concurrency::agent
{
public:
explicit file_reader(const std::string& file_name,
concurrency::ITarget<std::string>& target)
: _file_name(file_name)
, _target(target)
{
}
explicit file_reader(const std::string& file_name,
concurrency::ITarget<std::string>& target,
concurrency::Scheduler& scheduler)
: agent(scheduler)
, _file_name(file_name)
, _target(target)
{
}
explicit file_reader(const std::string& file_name,
concurrency::ITarget<std::string>& target,
concurrency::ScheduleGroup& group)
: agent(group)
, _file_name(file_name)
, _target(target)
{
}
// Retrieves any error that occurs during the life of the agent.
bool get_error(std::exception& e)
{
return try_receive(_error, e);
}
protected:
void run()
{
FILE* stream;
try
{
// Open the file.
if (fopen_s(&stream, _file_name.c_str(), "r") != 0)
{
// Throw an exception if an error occurs.
throw std::exception("Failed to open input file.");
}
// Create a buffer to hold file data.
char buf[1024];
// Set the buffer size.
setvbuf(stream, buf, _IOFBF, sizeof buf);
// Read the contents of the file and send the contents
// to the target.
while (fgets(buf, sizeof buf, stream))
{
asend(_target, std::string(buf));
}
// Send the empty string to the target to indicate the end of processing.
asend(_target, std::string(""));
// Close the file.
fclose(stream);
}
catch (const std::exception& e)
{
// Send the empty string to the target to indicate the end of processing.
asend(_target, std::string(""));
// Write the exception to the error buffer.
send(_error, e);
}
// Set the status of the agent to agent_done.
done();
}
private:
std::string _file_name;
concurrency::ITarget<std::string>& _target;
concurrency::overwrite_buffer<std::exception> _error;
};
[Premières]
Utilisation de la classe file_reader dans l'application
Cette section indique comment utiliser la classe file_reader pour lire le contenu d'un fichier texte. Il indique également comment créer un objet concurrency::call qui reçoit ces données du fichier et calcule leur somme de contrôle Adler-32.
Pour utiliser la classe file_reader dans votre application
Dans BasicAgent.cpp, ajoutez l'instruction #include suivante.
#include "file_reader.h"
Dans BasicAgent.cpp, ajoutez les directives using suivantes.
using namespace concurrency; using namespace std;
Dans la fonction _tmain, créez un objet concurrency::event qui signale la fin du traitement.
event e;
Créez un objet call qui met à jour la somme de contrôle lorsqu'il reçoit des données.
// The components of the Adler-32 sum. unsigned int a = 1; unsigned int b = 0; // A call object that updates the checksum when it receives data. call<string> calculate_checksum([&] (string s) { // If the input string is empty, set the event to signal // the end of processing. if (s.size() == 0) e.set(); // Perform the Adler-32 checksum algorithm. for_each(begin(s), end(s), [&] (char c) { a = (a + c) % 65521; b = (b + a) % 65521; }); });
Cet objet call définit également l'objet event lorsqu'il reçoit la chaîne vide pour signaler la fin du traitement.
Créez un objet file_reader qui lit à partir du fichier test.txt et écrit le contenu de ce fichier dans l'objet call.
file_reader reader("test.txt", calculate_checksum);
Démarrez l'agent et attendez qu'il ait terminé.
reader.start(); agent::wait(&reader);
Attendez que l'objet call reçoive toutes les données et se termine.
e.wait();
Vérifiez si le lecteur de fichier contient des erreurs. Si aucune erreur ne s'est produite, calculez la somme Adler-32 finale et imprimez-la sur la console.
std::exception error; if (reader.get_error(error)) { wcout << error.what() << endl; } else { unsigned int adler32_sum = (b << 16) | a; wcout << L"Adler-32 sum is " << hex << adler32_sum << endl; }
L'exemple suivant montre le fichier BasicAgent.cpp complet.
// BasicAgent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "file_reader.h"
using namespace concurrency;
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
// An event object that signals the end of processing.
event e;
// The components of the Adler-32 sum.
unsigned int a = 1;
unsigned int b = 0;
// A call object that updates the checksum when it receives data.
call<string> calculate_checksum([&] (string s) {
// If the input string is empty, set the event to signal
// the end of processing.
if (s.size() == 0)
e.set();
// Perform the Adler-32 checksum algorithm.
for_each(begin(s), end(s), [&] (char c) {
a = (a + c) % 65521;
b = (b + a) % 65521;
});
});
// Create the agent.
file_reader reader("test.txt", calculate_checksum);
// Start the agent and wait for it to complete.
reader.start();
agent::wait(&reader);
// Wait for the call object to receive all data and complete.
e.wait();
// Check the file reader for errors.
// If no error occurred, calculate the final Adler-32 sum and print it
// to the console.
std::exception error;
if (reader.get_error(error))
{
wcout << error.what() << endl;
}
else
{
unsigned int adler32_sum = (b << 16) | a;
wcout << L"Adler-32 sum is " << hex << adler32_sum << endl;
}
}
[Premières]
Exemple d'entrée
Voici l'exemple de contenu du fichier d'entrée text.txt :
Résultat de l'exemple
En cas d'utilisation avec l'exemple d'entrée, ce programme génère la sortie suivante :
Programmation fiable
Pour empêcher l'accès simultané aux membres de données, nous vous recommandons d'ajouter des méthodes qui effectuent le travail à la section protected ou private de votre classe. Vous devez ajouter à la section public de votre classe uniquement des méthodes qui envoient ou reçoivent des messages à ou en provenance de l'agent.
Appelez toujours la méthode concurrency::agent::done pour déplacer votre agent vers l'état terminé. On appelle en général cette méthode avant le retour de la méthode run.
Étapes suivantes
Pour obtenir un autre exemple d'une application basée sur agent, consultez Procédure pas à pas : utilisation de la classe join pour empêcher l'interblocage.
Voir aussi
Tâches
Procédure pas à pas : utilisation de la classe join pour empêcher l'interblocage
Concepts
Bibliothèque d'agents asynchrones