Compartir a través de


Tutorial: Crear una aplicación basada en agente

En este tema se describe cómo crear una aplicación basada en agente básica. En este tutorial, puede crear un agente que lee datos de un archivo de texto de forma asincrónica. La aplicación utiliza el algoritmo de suma de comprobación Adler-32 para calcular la suma de comprobación del contenido de ese archivo.

Requisitos previos

Para completar este tutorial, debe comprender los siguientes temas:

Secciones

En este tutorial se muestra cómo realizar las tareas siguientes:

Crear la aplicación de consola

En esta sección se muestra cómo crear una aplicación de consola de C++ que hace referencia a los archivos de encabezado que el programa usará. Los pasos iniciales varían según la versión de Visual Studio que use. Para ver la documentación de su versión preferida de Visual Studio, use el control de selector Versión. Se encuentra en la parte superior de la tabla de contenido de esta página.

Creación de una aplicación de consola C++ en Visual Studio

  1. En el menú principal, seleccione Archivo>Nuevo>Proyecto para abrir el cuadro de diálogo Crear nuevo proyecto.

  2. En la parte superior del cuadro de diálogo, establezca Lenguaje en C++, establezca Plataforma en Windows y Tipo de proyecto en Consola.

  3. En la lista de plantillas de proyecto, seleccione Aplicación de consola y Siguiente. En la siguiente página, escriba BasicAgent como nombre para el proyecto y, si lo desea, especifique la ubicación del proyecto.

  4. Elija el botón Crear para crear el proyecto.

  5. Haga clic con el botón derecho en el nodo del proyecto en el Explorador de soluciones y elija Propiedades. En Propiedades de configuración>C/C++>Encabezados precompilados>Encabezado precompilado, elija Crear.

Creación de una aplicación de consola C++ en Visual Studio 2017 y versiones anteriores

  1. En el menú Archivo, haga clic en Nuevo y, a continuación, haga clic en Proyecto para abrir el cuadro de diálogo Nuevo proyecto.

  2. En el cuadro de diálogo Nuevo proyecto, en el panel Tipos de proyecto, seleccione el nodo Visual C++ y, a continuación, seleccione Aplicación de consola Win32 en el panel Plantillas. Escriba un nombre para el proyecto, por ejemplo, BasicAgent, y a continuación haga clic en Aceptar para mostrar el Asistente para aplicación de consola Win32.

  3. En el cuadro de diálogo Asistente para aplicación de consola Win32, haga clic en Finalizar.

Actualice el archivo de encabezado

En el archivo pch.h (stdafx.h en Visual Studio 2017 y versiones anteriores), agregue el código siguiente:

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

El archivo de encabezado agents.h contiene la funcionalidad de la clase concurrency::agent.

Compruebe la aplicación

Por último, compruebe que la aplicación se creó correctamente; para ello, compílela y ejecútela. Para compilar la aplicación, en el menú Compilar, haga clic en Compilar solución. Si la aplicación se compila correctamente, haga clic en Iniciar depuración en el menú Depurar para ejecutarla.

[Arriba]

Creación de la clase file_reader

En esta sección se muestra cómo crear la clase file_reader. El runtime programa cada agente para realizar el trabajo en su propio contexto. Por tanto, puede crear un agente que realice el trabajo de forma sincrónica pero que interactúe con otros componentes de forma asincrónica. La clase file_reader lee los datos de un archivo de entrada determinado y los envía desde este archivo a un componente de destino determinado.

Para crear la clase file_reader

  1. Agregue un nuevo archivo de encabezado de C++ al proyecto. Para ello, en el Explorador de soluciones, haga clic con el botón derecho en el nodo Archivos de encabezado, haga clic en Agregar y, a continuación, haga clic en Nuevo elemento. En el panel Plantillas, seleccione Archivo de encabezado (.h). En el cuadro de diálogo Agregar nuevo elemento, escriba file_reader.h en el cuadro Nombre y haga clic en Agregar.

  2. En file_reader.h, agregue el siguiente código.

    #pragma once
    
  3. En file_reader.h, cree una clase de nombre file_reader que derive de agent.

    class file_reader : public concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. Agregue los siguientes miembros de datos a la sección private de la clase.

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

    El miembro _file_name es el nombre de archivo del que lee el agente. El miembro _target es un objeto concurrency::ITarget en el que el agente escribe el contenido del archivo. El miembro _error contiene cualquier error que se produce durante la vida del agente.

  5. Agregue el código siguiente para los constructores de file_reader en la sección public de la clase 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)
    {
    }
    

    Cada sobrecarga de constructor establece los miembros de datos de file_reader. La segunda y tercera sobrecarga de constructor permiten que la aplicación use un programador concreto con el agente. La primera sobrecarga utiliza el programador predeterminado con el agente.

  6. Agregue el método get_error a la sección pública de la clase file_reader.

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

    El método get_error recupera cualquier error que se produce durante la vida del agente.

  7. Implemente el método concurrency::agent::run en la sección protected de la clase.

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

El método run abre el archivo y lee sus datos. El método run utiliza el control de excepciones para capturar los errores que se produzcan durante el procesamiento del archivo.

Cada vez que este método lee datos del archivo, llama a la función concurrency::asend para enviar esos datos al búfer de destino. Envía la cadena vacía a su búfer de destino para indicar el fin del procesamiento.

En el ejemplo siguiente se muestra el contenido completo 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;
};

[Arriba]

Uso de la clase file_reader en la aplicación

En esta sección se muestra cómo utilizar la clase file_reader para leer el contenido de un archivo de texto. También se muestra cómo crear un objeto concurrency::call que recibe los datos de este archivo y calcula su suma de comprobación Adler-32.

Para utilizar la clase file_reader en la aplicación

  1. En BasicAgent.cpp, agregue la siguiente instrucción #include.

    #include "file_reader.h"
    
  2. En BasicAgent.cpp, agregue las siguientes directivas using.

    using namespace concurrency;
    using namespace std;
    
  3. En la función _tmain, cree un objeto concurrency::event que indique el fin del procesamiento.

    event e;
    
  4. Cree un objeto call que actualice la suma de comprobación cuando reciba datos.

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

    Este objeto call también establece el objeto event cuando recibe la cadena vacía para señalar el fin del procesamiento.

  5. Cree un objeto file_reader que lea del archivo test.txt y escriba el contenido de este archivo en el objeto call.

    file_reader reader("test.txt", calculate_checksum);
    
  6. Inicie el agente y espere a que finalice.

    reader.start();
    agent::wait(&reader);
    
  7. Espere a que el objeto call reciba todos los datos y finalice.

    e.wait();
    
  8. Compruebe los errores del lector del archivo. Si no se ha producido ningún error, calcule la suma Adler-32 final e imprímala en la consola.

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

En el ejemplo siguiente se muestra el archivo BasicAgent.cpp completo.

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

[Arriba]

Entrada de ejemplo

Este es el contenido de ejemplo del archivo de entrada text.txt:

The quick brown fox
jumps
over the lazy dog

Salida de ejemplo

Cuando se utiliza con la entrada de ejemplo, este programa produce el siguiente resultado:

Adler-32 sum is fefb0d75

Programación sólida

Para evitar el acceso simultáneo a los miembros de datos, se recomienda agregar métodos que realicen el trabajo en las secciones protected o private de la clase. Agregue únicamente los métodos que envían o reciben mensajes del agente en la sección public de la clase.

Llame siempre al método concurrency::agent::done para mover el agente al estado completado. Por lo general se llama a este método antes de volver del método run.

Pasos siguientes

Para obtener otro ejemplo de una aplicación basada en agente, vea Tutorial: Usar la clase join para evitar un interbloqueo.

Consulte también

Biblioteca de agentes asincrónicos
Bloques de mensajes asincrónicos
Funciones que pasan mensajes
Estructuras de datos de sincronización
Tutorial: Usar la clase join para evitar un interbloqueo