Пошаговое руководство. Создание приложения на основе агента
В этом разделе описывается создание базового приложения на основе агента. В этом пошаговом руководстве можно создать агент, который считывает данные из текстового файла асинхронно. Приложение использует алгоритм контрольной суммы Adler-32 для вычисления контрольной суммы содержимого этого файла.
Необходимые компоненты
Для выполнения этого пошагового руководства необходимо понять следующие разделы:
Разделы
В этом пошаговом руководстве показано, как выполнить следующие задачи:
Создание консольного приложения
В этом разделе показано, как создать консольное приложение C++, которое ссылается на файлы заголовков, которые будет использоваться программой. Начальные шаги зависят от используемой версии Visual Studio. Чтобы ознакомиться с документацией по предпочтительной версии Visual Studio, используйте селектор Версия. Он находится в верхней части оглавления на этой странице.
Создание консольного приложения C++ в Visual Studio
В главном меню выберите Файл >Создать >Проект, чтобы открыть диалоговое окно Создание проекта.
В верхней части диалогового окна задайте для параметра Язык значение C++, для параметра Платформа значение Windows, а для Типа проекта — Консоль.
В отфильтрованном списке типов проектов щелкните Консольное приложение, а затем нажмите кнопку Далее. На следующей странице введите
BasicAgent
имя проекта и укажите расположение проекта при необходимости.Нажмите кнопку Создать, чтобы создать проект.
Щелкните правой кнопкой мыши узел проекта в Обозреватель решений и выберите пункт "Свойства". В разделе свойств>конфигурации C/C++>Precompiled Headers Precompiled headers (Предварительно скомпилированные заголовки>) нажмите кнопку Create (Создать).
Создание консольного приложения C++ в Visual Studio 2017 и более ранних версиях
В меню "Файл" нажмите кнопку "Создать", а затем выберите "Проект", чтобы отобразить диалоговое окно "Новый проект".
В диалоговом окне "Создать проект" выберите узел Visual C++ в области "Типы проектов" и выберите консольное приложение Win32 на панели "Шаблоны". Введите имя проекта, например, и нажмите кнопку "ОК",
BasicAgent
чтобы отобразить мастер консольного приложения Win32.В диалоговом окне мастера консольного приложения Win32 нажмите кнопку "Готово".
Обновление файла заголовка
В файле pch.h (stdafx.h в Visual Studio 2017 и более ранних версиях) добавьте следующий код:
#include <agents.h>
#include <string>
#include <iostream>
#include <algorithm>
Файл заголовка agent.h содержит функциональные возможности класса concurrency::agent.
Проверка приложения
Наконец, убедитесь, что приложение было успешно создано путем создания и запуска приложения. Чтобы создать приложение, в меню "Сборка" нажмите кнопку "Создать решение". Если приложение успешно строится, запустите приложение, нажав кнопку "Начать отладку " в меню отладки .
[В начало]
Создание класса file_reader
В этом разделе показано, как создать file_reader
класс. Среда выполнения планирует выполнение каждого агента в собственном контексте. Таким образом, можно создать агент, который выполняет синхронную работу, но взаимодействует с другими компонентами асинхронно. Класс file_reader
считывает данные из заданного входного файла и отправляет данные из этого файла в заданный целевой компонент.
Создание класса file_reader
Добавьте новый файл заголовка C++ в проект. Для этого щелкните правой кнопкой мыши узел "Файлы заголовков" в Обозреватель решений, нажмите кнопку "Добавить" и выберите пункт "Создать элемент". В области "Шаблоны" выберите файл заголовка (.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
является объектом параллелизма::ITarget , в который агент записывает содержимое файла. Член_error
содержит любую ошибку, возникающую во время существования агента.Добавьте следующий код для
file_reader
конструкторов вpublic
раздел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
получает любую ошибку, возникающую во время существования агента.Реализуйте метод параллелизма::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
использует обработку исключений для записи ошибок, возникающих во время обработки файлов.
Каждый раз, когда этот метод считывает данные из файла, он вызывает функцию параллелизма::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
класс для чтения содержимого текстового файла. В нем также показано, как создать объект параллелизма::call , который получает эти данные файла и вычисляет свою контрольную сумму Adler-32.
Использование класса file_reader в приложении
В BasicAgent.cpp добавьте следующую
#include
инструкцию.#include "file_reader.h"
В BasicAgent.cpp добавьте следующие
using
директивы.using namespace concurrency; using namespace std;
_tmain
В функции создайте объект параллелизма::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 "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;
}
}
[В начало]
Пример ввода
Это пример содержимого входного файла text.txt:
The quick brown fox
jumps
over the lazy dog
Образец вывода
При использовании с примером входных данных эта программа создает следующие выходные данные:
Adler-32 sum is fefb0d75
Отказоустойчивость
Чтобы предотвратить одновременный доступ к членам данных, рекомендуется добавить методы, которые выполняют работу в protected
класс или private
раздел. Добавьте только методы, отправляющие или получающие сообщения в агент или из агента в public
раздел класса.
Всегда вызывайте метод параллелизма::agent::d one , чтобы переместить агент в готовое состояние. Обычно этот метод вызывается перед возвратом run
из метода.
Next Steps
Другой пример приложения на основе агента см. в пошаговом руководстве. Использование соединения для предотвращения взаимоблокировки.
См. также
Библиотека асинхронных агентов
Асинхронные блоки сообщений
Функции передачи сообщений
Структуры данных синхронизации
Пошаговое руководство. Использование класса join для предотвращения взаимоблокировки