Пошаговое руководство. Создание приложения на основе агента
В этом разделе описывается создание основного приложения на основе агентов. В этом пошаговом руководстве показано создание агента, который асинхронно считывает данные из текстового файла. Приложение использует алгоритм контрольной суммы Adler-32 для расчета контрольной суммы содержимого этого файла.
Обязательные компоненты
Перед работой с этим пошаговым руководством необходимо ознакомиться со следующими разделами.
Подразделы
В этом пошаговом руководстве показано выполнение следующих задач.
Создание консольного приложения
Создание класса file_reader
Использование класса file_reader в приложении
Создание консольного приложения
В этом подразделе показано создание консольного приложения Visual C++, ссылающегося на файлы заголовков, которые будут использоваться в программе.
Создание приложения Visual C++ с помощью мастера консольных приложений Win32
В меню Файл последовательно щелкните Создать и Проект, чтобы открыть диалоговое окно Новый проект.
В диалоговом окне Новый проект в области Типы проектов выберите узел Visual C++, затем выберите Консольное приложение Win32 в области Шаблоны. Введите имя проекта, например BasicAgent, затем нажмите кнопку ОК, чтобы отобразить Мастер консольных приложений Win32.
В диалоговом окне Мастера консольных приложений Win32 нажмите кнопку Готово.
Добавьте в файл stdafx.h следующий код.
#include <agents.h> #include <string> #include <iostream> #include <algorithm>
Заголовочный файл agents.h содержит функциональные возможности класса concurrency::agent.
Убедитесь, что приложение было успешно создано, выполнив его построение и запуск. Для построения приложения в меню Построение выберите команду Построить решение. Если построение приложения выполнено успешно, запустите приложение, нажав кнопку Начать отладку в меню Отладка.
[Наверх]
Создание класса file_reader
В этом подразделе показано, как создать класс file_reader. Среда выполнения планирует выполнение работы каждым агентом в его собственном контексте. Следовательно, можно создать агент, выполняющий работу синхронно, но взаимодействующий с другими компонентами асинхронно. Класс file_reader считывает данные из заданного входного файла и отправляет данные из этого файла заданному целевому компоненту.
Создание класса file_reader
Добавьте в проект новый файл заголовка С++. Для этого в обозревателе решений щелкните правой кнопкой мыши узел Файлы заголовков, щелкните Добавить и выберите Новый элемент. В области Шаблоны выберите пункт Файл заголовка (.h). В диалоговом окне Добавление нового элемента введите file_reader.h в поле Имя и нажмите кнопку Добавить.
Добавьте следующий код в файл file_reader.h.
#pragma once
В файле file_reader.h создайте класс с именем file_reader, производный от agent.
class file_reader : public concurrency::agent { public: protected: private: };
В секцию private этого класса добавьте следующие члены данных.
std::string _file_name; concurrency::ITarget<std::string>& _target; concurrency::overwrite_buffer<std::exception> _error;
Член _file_name представляет собой имя файла, из которого выполняет считывание агент. Член _target представляет собой объект concurrency::ITarget, в который агент записывает содержимое файла. Член _error содержит все ошибки, происходящие за время существования агента.
Добавьте в секцию public класса file_reader следующий код для конструкторов 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) { }
Каждая перегрузка конструктора задает члены данных file_reader. Вторая и третья перегрузки конструктора позволяют приложению использовать с агентом конкретный планировщик. Первая перегрузка использует с агентом планировщик по умолчанию.
Добавьте метод get_error в открытую секцию класса file_reader.
bool get_error(std::exception& e) { return try_receive(_error, e); }
Метод get_error извлекает любые ошибки, происходящие за время существования агента.
Реализуйте метод concurrency::agent::run в разделе 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(); }
Метод run открывает файл и считывает из него данные. Метод run использует обработку исключений для фиксирования любых ошибок, происходящих во время обработки файлов.
При каждом считывании данных из файла этот метод вызывает функцию concurrency::asend, чтобы отправить данные в целевой буфер. Он отправляет в целевой буфер пустую строку, чтобы указать, что обработка завершена.
В следующем примере показано полное содержимое файла 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;
};
[Наверх]
Использование класса file_reader в приложении
В этом подразделе показано использование класса file_reader для чтения содержимого текстового файла. Кроме того, в нем показано, как создавать объект concurrency::call, который получает данные файла и вычисляет их контрольную сумму Adler-32.
Использование класса file_reader в приложении
В файл BasicAgent.cpp добавьте следующий оператор #include.
#include "file_reader.h"
В файл BasicAgent.cpp добавьте следующие директивы using.
using namespace concurrency; using namespace std;
В функции _tmain создайте объект concurrency::event, сигнализирующий о завершении обработки.
event e;
Создайте объект call, обновляющий контрольную сумму при получении данных.
// 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; }); });
При получении пустой строки в качестве сигнала о завершении обработки этот объект call также задает объект event.
Создайте объект file_reader, считывающий данные из файла test.txt и записывающий содержимое этого файла в объект call.
file_reader reader("test.txt", calculate_checksum);
Запустите агент и дождитесь его завершения.
reader.start(); agent::wait(&reader);
Дождитесь получения объектом call всех данных и завершения его работы.
e.wait();
Проверьте наличие ошибок в модуле чтения файлов. Если ошибок не обнаружено, рассчитайте окончательную сумму Adler-32 и выведите сумму на консоль.
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; }
В следующем примере показан полный файл BasicAgent.cpp.
// 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;
}
}
[Наверх]
Пример ввода
Далее представлен пример содержимого входного файла text.txt.
Пример результатов выполнения
Если используются входные данные, представленные в примере, программа на выходе дает следующий результат.
Надежное программирование
Чтобы запретить одновременный доступ к членам данных, рекомендуется добавить методы, выполняющие работу, в секцию protected или private используемого класса. В секцию public этого класса следует добавлять только методы, которые отправляют сообщения в агент или получают сообщения от него.
Всегда вызывайте метод concurrency::agent::done для перевода агента в завершенное состояние. Как правило, этот метод вызывается до возврата метода run.
Следующие действия
Еще один пример приложения на основе агентов см. в разделе Пошаговое руководство. Использование класса join для предотвращения взаимоблокировки.
См. также
Задачи
Пошаговое руководство. Использование класса join для предотвращения взаимоблокировки