Instruções passo a passo: criando um aplicativo com base no agente

Este tópico descreve como criar um aplicativo básico baseado no agente. Neste passo a passo, você pode criar um agente que lê dados de um arquivo de texto de forma assíncrona. O aplicativo usa o algoritmo de soma de verificação Adler-32 para calcular a soma de verificação do conteúdo desse arquivo.

Pré-requisitos

Você deve entender os seguintes tópicos para concluir este passo a passo:

Seções

Este passo a passo demonstra como realizar as seguintes tarefas:

Criando o Aplicativo de Console

Esta seção mostra como criar um aplicativo de console do C++ que faz referência aos arquivos de cabeçalho que o programa usará. As etapas iniciais variam dependendo da versão do Visual Studio que você está usando. Para ver a documentação da sua versão preferencial do Visual Studio, use o controle seletor de Versão. Ele é encontrado na parte superior da tabela de conteúdo nesta página.

Para criar um aplicativo de console do C++ no Visual Studio

  1. No menu principal, escolha Arquivo>Novo>Projeto para abrir a caixa de diálogo Criar um projeto.

  2. Na parte superior da caixa de diálogo, defina Linguagem como C++, Plataforma como Windows e Tipo de projeto como Console.

  3. Na lista filtrada de tipos de projeto, escolha Aplicativo de Console e, em seguida, escolha Avançar. Na próxima página, insira BasicAgent como nome do projeto e, se quiser, especifique o local do projeto.

  4. Escolha o botão Criar para criar o projeto.

  5. Clique com o botão direito do mouse no nó do projeto no Gerenciador de Soluções e selecione Propriedades. Em Propriedades de configuração>C/C++>Cabeçalhos pré-compilados>Cabeçalho pré-compilado, escolha Criar.

Para criar um aplicativo de console do C++ no Visual Studio 2017 e mais recente

  1. No menu Arquivo, clique em Novo e clique em Projeto para exibir a caixa de diálogo Novo Projeto.

  2. Na caixa de diálogo Novo projeto, selecione o nó Visual C++ no painel Tipos de projeto e selecione Aplicativo de Console do Win32 no painel Modelos. Digite um nome para o projeto, como, por exemplo, BasicAgent, e clique em OK para exibir o Assistente do Aplicativo do Console do Win32.

  3. Na caixa de diálogo Assistente do Aplicativo de Console do Win32, clique em Concluir.

Atualizar o arquivo de cabeçalho

No arquivo pch.h (stdafx.h no Visual Studio 2017 e mais recente), adicione o seguinte código:

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

O arquivo de cabeçalho agents.h contém a funcionalidade da classe concurrency::agent.

Verifique o aplicativo

Por fim, verifique se o aplicativo foi criado com êxito compilando e executando-o. Para compilar o aplicativo, no menu Compilar, clique em Compilar Solução. Se o aplicativo for compilado com êxito, execute-o clicando em Iniciar Depuração no menu Depurar.

[Parte superior]

Criando a classe file_reader

Esta seção mostra como criar a classe file_reader. O runtime agenda cada agente para executar o trabalho em seu próprio contexto. Portanto, você pode criar um agente que executa o trabalho de forma síncrona, mas interage com outros componentes de forma assíncrona. A classe file_reader lê dados de um determinado arquivo de entrada e envia dados desse arquivo para um determinado componente de destino.

Para criar a classe file_reader

  1. Um novo arquivo de cabeçalho do C++ para o seu projeto. Para fazer isso, clique com o botão direito do mouse no nó Arquivos de cabeçalho no Gerenciador de Soluções, clique em Adicionar e em Novo item. No painel Modelos, escolha Header File (.h). Na caixa de diálogo Adicionar novo item, insira file_reader.h na caixa Nome e clique em Adicionar.

  2. Em file_reader.h, adicione o código a seguir.

    #pragma once
    
  3. Em file_reader.h, crie uma classe nomeada file_reader que deriva de agent.

    class file_reader : public concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. Adicione os membros de dados a seguir à seção private da sua classe.

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

    O membro _file_name é o nome do arquivo do qual o agente lê. O membro _target é um objeto concurrency::ITarget no qual o agente grava o conteúdo do arquivo. O membro _error contém qualquer erro que ocorra durante a vida útil do agente.

  5. Adicione o código a seguir para os construtores file_reader à seção public da 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)
    {
    }
    

    Cada sobrecarga de construtor define os membros de dados file_reader. A segunda e terceira sobrecargas de construtor permitem que seu aplicativo use um agendador específico com seu agente. A primeira sobrecarga usa o agendador padrão com seu agente.

  6. Adicione o método get_error à seção pública da classe file_reader.

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

    O método get_error recupera qualquer erro que ocorra durante a vida útil do agente.

  7. Implemente o método concurrency::agent::run na seção protected da sua 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();
    }
    

O método run abre o arquivo e lê dados a partir dele. O método run usa o tratamento de exceção para capturar os erros que ocorrem durante o processamento de arquivos.

Cada vez que esse método lê dados do arquivo, ele chama a função concurrency::asend para enviar esses dados para o buffer de destino. Ele envia a cadeia de caracteres vazia para o buffer de destino para indicar o fim do processamento.

O exemplo a seguir mostra o conteúdo 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;
};

[Parte superior]

Usando a classe file_reader no aplicativo

Esta seção mostra como usar a classe file_reader para ler o conteúdo de um arquivo de texto. Também mostra como criar um objeto concurrency::call que recebe esses dados de arquivo e calcula sua soma de verificação Adler-32.

Para usar a classe file_reader no aplicativo

  1. Em BasicAgent.cpp, adicione a instrução #include a seguir.

    #include "file_reader.h"
    
  2. Em BasicAgent.cpp, adicione a instrução using a seguir.

    using namespace concurrency;
    using namespace std;
    
  3. Na função _tmain, crie um objeto concurrency::event que sinaliza o fim do processamento.

    event e;
    
  4. Crie um objeto call que atualize a soma de verificação quando receber dados.

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

    Esse objeto call também define o objeto event quando recebe a cadeia de caracteres vazia para sinalizar o fim do processamento.

  5. Crie um objeto file_reader que lê a partir do arquivo test.txt e grava o conteúdo desse arquivo no objeto call.

    file_reader reader("test.txt", calculate_checksum);
    
  6. Inicie o agente e aguarde a conclusão.

    reader.start();
    agent::wait(&reader);
    
  7. Aguarde até que o objeto call receba todos os dados e conclua.

    e.wait();
    
  8. Verifique se há erros no leitor de arquivos. Se não houver erro, calcule a soma final do Adler-32 e imprima a soma no 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;
    }
    

O exemplo a seguir mostra o arquivo 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;
   }
}

[Parte superior]

Entrada de Exemplo

Este é o conteúdo de exemplo do arquivo de entrada text.txt:

The quick brown fox
jumps
over the lazy dog

Saída de exemplo

Quando usado com a entrada de exemplo, este programa produz a seguinte saída:

Adler-32 sum is fefb0d75

Programação robusta

Para impedir o acesso simultâneo aos membros de dados, recomendamos que você adicione métodos que executam o trabalho à seção protected ou private à sua classe. Adicione apenas métodos que enviem ou recebam mensagens de ou para o agente à seção public de sua classe.

Sempre chame o método concurrency::agent::done para mover o agente para o estado concluído. Normalmente, você chama esse método antes de retornar do método run.

Próximas etapas

Para obter outro exemplo de um aplicativo baseado em agente, consulte Passo a passo: Usar a junção para evitar deadlock.

Confira também

Biblioteca de agentes assíncronos
Blocos de mensagens assíncronos
Funções de transmissão de mensagem
Estruturas de dados de sincronização
Instruções passo a passo: usando unir para evitar deadlock