Partager via


Procédure pas à pas : création d’une application basée sur un agent

Cette rubrique explique comment créer une application basée sur un agent de base. Dans cette procédure pas à pas, vous pouvez créer un agent qui lit les données à partir d’un fichier texte de manière asynchrone. L’application utilise l’algorithme de somme de contrôle Adler-32 pour calculer la somme de contrôle du contenu de ce fichier.

Prérequis

Vous devez comprendre les rubriques suivantes pour effectuer cette procédure pas à pas :

Sections

Cette procédure pas à pas montre comment effectuer les tâches suivantes :

Créer l'application console

Cette section montre comment créer une application console C++ qui référence les fichiers d’en-tête que le programme utilisera. Les étapes initiales varient selon la version de Visual Studio que vous utilisez. Pour consulter la documentation sur votre version préférée de Visual Studio, utilisez le contrôle de sélection de Version . Il se trouve en haut de la table des matières de cette page.

Pour créer une application console C++ dans Visual Studio

  1. Dans le menu principal, choisissez Fichier>Nouveau>Projet pour ouvrir la boîte de dialogue Créer un projet.

  2. En haut de la boîte de dialogue, définissez Langage sur C++, Plateforme sur Windows et Type de projet sur Console.

  3. À partir de la liste des types de projets, choisissez Application console, puis choisissez Suivant. Dans la page suivante, entrez BasicAgent le nom du projet et spécifiez l’emplacement du projet si vous le souhaitez.

  4. Choisissez le bouton Créer pour créer le projet.

  5. Cliquez avec le bouton droit sur le nœud du projet dans Explorateur de solutions, puis choisissez Propriétés. Sous Propriétés>de configuration C/C++>En-têtes> précompilés, choisissez Créer.

Pour créer une application console C++ dans Visual Studio 2017 et versions antérieures

  1. Dans le menu Fichier , cliquez sur Nouveau, puis sur Projet pour afficher la boîte de dialogue Nouveau projet .

  2. 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, BasicAgentpuis cliquez sur OK pour afficher l’Assistant Application console Win32.

  3. Dans la boîte de dialogue Assistant Application console Win32, cliquez sur Terminer.

Mettre à jour le fichier d’en-tête

Dans le fichier pch.h (stdafx.h dans Visual Studio 2017 et versions antérieures), ajoutez le code suivant :

#include <agents.h>
#include <string>
#include <iostream>
#include <algorithm>

Le fichier d’en-tête agents.h contient les fonctionnalités de la classe concurrency ::agent .

Vérifier l’application

Enfin, vérifiez que l’application a été créée avec succès en créant 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 l’application en cliquant sur Démarrer le débogage dans le menu Débogage .

[Haut]

Création de la classe file_reader

Cette section montre comment créer la file_reader classe. Le runtime planifie chaque agent pour effectuer le travail dans son propre contexte. Par conséquent, vous pouvez créer un agent qui fonctionne de façon synchrone, mais interagit avec d’autres composants de manière asynchrone. La file_reader classe lit les données d’un fichier d’entrée donné et envoie des données de ce fichier à un composant cible donné.

Pour créer la classe file_reader

  1. Ajoutez un nouveau fichier d’en-tête C++ à votre projet. Pour ce faire, cliquez avec le bouton droit sur le nœud Fichiers d’en-tête dans 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 la zone Nom , puis cliquez sur Ajouter.

  2. Dans file_reader.h, ajoutez le code suivant.

    #pragma once
    
  3. Dans file_reader.h, créez une classe nommée file_reader qui dérive de agent.

    class file_reader : public concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. Ajoutez les membres de données suivants à la private section de votre classe.

    std::string _file_name;
    concurrency::ITarget<std::string>& _target;
    concurrency::overwrite_buffer<std::exception> _error;
    

    Le _file_name membre est le nom de fichier à partir duquel l’agent lit. Le _target membre est un objet concurrency ::ITarget dans lequel l’agent écrit le contenu du fichier. Le _error membre contient toute erreur qui se produit pendant la durée de vie de l’agent.

  5. Ajoutez le code suivant pour les file_reader constructeurs à la public section de la file_reader classe.

    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 file_reader données. La deuxième et la troisième surcharge du constructeur permettent à votre application d’utiliser un planificateur spécifique avec votre agent. La première surcharge utilise le planificateur par défaut avec votre agent.

  6. Ajoutez la get_error méthode à la section publique de la file_reader classe.

    bool get_error(std::exception& e)
    {
       return try_receive(_error, e);
    }
    

    La get_error méthode récupère toute erreur qui se produit pendant la durée de vie de l’agent.

  7. Implémentez la méthode concurrency ::agent ::run dans la protected section 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 run méthode ouvre le fichier et lit les données à partir de celui-ci. La méthode utilise la run gestion des exceptions pour capturer les erreurs qui se produisent pendant le traitement des fichiers.

Chaque fois que cette méthode lit les données du fichier, elle appelle la fonction concurrency ::asend pour envoyer ces données à la mémoire tampon cible. Il envoie la chaîne vide à sa mémoire tampon cible pour indiquer la fin du traitement.

L’exemple suivant montre 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;
};

[Haut]

Utilisation de la classe file_reader dans l’application

Cette section montre comment utiliser la file_reader classe pour lire le contenu d’un fichier texte. Il montre également comment créer un objet concurrency ::call qui reçoit ces données de fichier et calcule sa somme de contrôle Adler-32.

Pour utiliser la classe file_reader dans votre application

  1. Dans BasicAgent.cpp, ajoutez l’instruction suivante #include .

    #include "file_reader.h"
    
  2. Dans BasicAgent.cpp, ajoutez les directives suivantes using .

    using namespace concurrency;
    using namespace std;
    
  3. Dans la _tmain fonction, créez un objet concurrency ::event qui signale la fin du traitement.

    event e;
    
  4. Créez un call objet 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 call objet définit également l’objet event lorsqu’il reçoit la chaîne vide pour signaler la fin du traitement.

  5. Créez un file_reader objet 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);
    
  6. Démarrez l’agent et attendez qu’il se termine.

    reader.start();
    agent::wait(&reader);
    
  7. Attendez que l’objet call reçoive toutes les données et se termine.

    e.wait();
    
  8. Vérifiez que le lecteur de fichier contient des erreurs. Si aucune erreur ne s’est produite, calculez la somme finale d’Adler-32 et imprimez la somme dans 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 de BasicAgent.cpp complet.

// BasicAgent.cpp : Defines the entry point for the console application.
//

#include "pch.h" // Use stdafx.h in Visual Studio 2017 and earlier
#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;
   }
}

[Haut]

Exemple d’entrée

Voici l’exemple de contenu du fichier d’entrée text.txt :

The quick brown fox
jumps
over the lazy dog

Exemple de sortie

Lorsqu’il est utilisé avec l’exemple d’entrée, ce programme produit la sortie suivante :

Adler-32 sum is fefb0d75

Programmation fiable

Pour empêcher l’accès simultané aux membres de données, nous vous recommandons d’ajouter des méthodes qui effectuent un travail à la ou private à la protected section de votre classe. Ajoutez uniquement des méthodes qui envoient ou reçoivent des messages vers ou depuis l’agent à la public section de votre classe.

Appelez toujours la méthode concurrency ::agent ::d one pour déplacer votre agent vers l’état terminé. Vous appelez généralement cette méthode avant de revenir de la run méthode.

Étapes suivantes

Pour obtenir un autre exemple d’application basée sur un agent, consultez Procédure pas à pas : Utilisation de la jointure pour empêcher l’interblocage.

Voir aussi

Bibliothèque d’agents asynchrones
Blocs de messages asynchrones
Fonctions de passage de messages
Structures de données de synchronisation
Procédure pas à pas : utilisation de la classe join pour empêcher l’interblocage