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
No menu principal, escolha Arquivo>Novo>Projeto para abrir a caixa de diálogo Criar um projeto.
Na parte superior da caixa de diálogo, defina Linguagem como C++, Plataforma como Windows e Tipo de projeto como Console.
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.Escolha o botão Criar para criar o projeto.
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
No menu Arquivo, clique em Novo e clique em Projeto para exibir a caixa de diálogo Novo Projeto.
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.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.
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
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.Em file_reader.h, adicione o código a seguir.
#pragma once
Em file_reader.h, crie uma classe nomeada
file_reader
que deriva deagent
.class file_reader : public concurrency::agent { public: protected: private: };
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.Adicione o código a seguir para os construtores
file_reader
à seçãopublic
da classefile_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.Adicione o método
get_error
à seção pública da classefile_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.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;
};
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
Em BasicAgent.cpp, adicione a instrução
#include
a seguir.#include "file_reader.h"
Em BasicAgent.cpp, adicione a instrução
using
a seguir.using namespace concurrency; using namespace std;
Na função
_tmain
, crie um objeto concurrency::event que sinaliza o fim do processamento.event e;
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 objetoevent
quando recebe a cadeia de caracteres vazia para sinalizar o fim do processamento.Crie um objeto
file_reader
que lê a partir do arquivo test.txt e grava o conteúdo desse arquivo no objetocall
.file_reader reader("test.txt", calculate_checksum);
Inicie o agente e aguarde a conclusão.
reader.start(); agent::wait(&reader);
Aguarde até que o objeto
call
receba todos os dados e conclua.e.wait();
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;
}
}
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