Пошаговое руководство. Создание приложения на основе агента

В этом разделе описывается создание основного приложения на основе агентов. В этом пошаговом руководстве показано создание агента, который асинхронно считывает данные из текстового файла. Приложение использует алгоритм контрольной суммы Adler-32 для расчета контрольной суммы содержимого этого файла.

Обязательные компоненты

Перед работой с этим пошаговым руководством необходимо ознакомиться со следующими разделами.

Подразделы

В этом пошаговом руководстве показано выполнение следующих задач.

  • Создание консольного приложения

  • Создание класса file_reader

  • Использование класса file_reader в приложении

Создание консольного приложения

В этом подразделе показано создание консольного приложения Visual C++, ссылающегося на файлы заголовков, которые будут использоваться в программе.

Создание приложения Visual C++ с помощью мастера консольных приложений Win32

  1. В меню Файл последовательно щелкните Создать и Проект, чтобы открыть диалоговое окно Новый проект.

  2. В диалоговом окне Новый проект в области Типы проектов выберите узел Visual C++, затем выберите Консольное приложение Win32 в области Шаблоны. Введите имя проекта, например BasicAgent, затем нажмите кнопку ОК, чтобы отобразить Мастер консольных приложений Win32.

  3. В диалоговом окне Мастера консольных приложений Win32 нажмите кнопку Готово.

  4. Добавьте в файл stdafx.h следующий код.

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

    Файл заголовка agents.h содержит функциональные возможности класса Concurrency::agent.

  5. Убедитесь, что приложение было успешно создано, выполнив его построение и запуск. Для построения приложения в меню Построение выберите команду Построить решение. Если построение приложения выполнено успешно, запустите приложение, нажав кнопку Начать отладку в меню Отладка.

[в начало]

Создание класса file_reader

В этом подразделе показано, как создать класс file_reader. Среда выполнения планирует выполнение работы каждым агентом в его собственном контексте. Следовательно, можно создать агент, выполняющий работу синхронно, но взаимодействующий с другими компонентами асинхронно. Класс file_reader считывает данные из заданного входного файла и отправляет данные из этого файла заданному целевому компоненту.

Создание класса file_reader

  1. Добавьте в проект новый файл заголовка С++. Для этого в обозревателе решений щелкните правой кнопкой мыши узел Файлы заголовков, щелкните Добавить и выберите Новый элемент. В области Шаблоны выберите пункт Файл заголовка (.h). В диалоговом окне Добавление нового элемента введите file_reader.h в поле Имя и нажмите кнопку Добавить.

  2. Добавьте следующий код в файл file_reader.h.

    #pragma once
    
  3. В файле file_reader.h создайте класс с именем file_reader, производный от agent.

    class file_reader : public Concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. В секцию private этого класса добавьте следующие члены данных.

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

    Член _file_name представляет собой имя файла, из которого выполняет считывание агент. Член _target представляет собой объект Concurrency::ITarget, в который агент записывает содержимое файла. Член _error содержит все ошибки, происходящие за время существования агента.

  5. Добавьте в секцию 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. Вторая и третья перегрузки конструктора позволяют приложению использовать с агентом конкретный планировщик. Первая перегрузка использует с агентом планировщик по умолчанию.

  6. Добавьте метод get_error в открытую секцию класса file_reader.

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

    Метод get_error извлекает любые ошибки, происходящие за время существования агента.

  7. Реализуйте метод 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 в приложении

  1. В файл BasicAgent.cpp добавьте следующий оператор #include.

    #include "file_reader.h"
    
  2. В файл BasicAgent.cpp добавьте следующие директивы using.

    using namespace Concurrency;
    using namespace std;
    
  3. В функции _tmain создайте объект Concurrency::event, сигнализирующий о завершении обработки.

    event e;
    
  4. Создайте объект 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(s.begin(), s.end(), [&] (char c) {
          a = (a + c) % 65521;
          b = (b + a) % 65521;
       });
    });
    

    При получении пустой строки в качестве сигнала о завершении обработки этот объект call также задает объект event.

  5. Создайте объект file_reader, считывающий данные из файла test.txt и записывающий содержимое этого файла в объект call.

    file_reader reader("test.txt", calculate_checksum);
    
  6. Запустите агент и дождитесь его завершения.

    reader.start();
    agent::wait(&reader);
    
  7. Дождитесь получения объектом call всех данных и завершения его работы.

    e.wait();
    
  8. Проверьте наличие ошибок в модуле чтения файлов. Если ошибок не обнаружено, рассчитайте окончательную сумму 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(s.begin(), s.end(), [&] (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 этого класса следует добавлять только методы, которые отправляют сообщения в агент или получают сообщения от него.

Всегда вызывайте метод Concurrency::agent::done для перевода агента в завершенное состояние. Как правило, этот метод вызывается до возврата метода run.

Следующие действия

Еще один пример приложения на основе агентов см. в разделе Пошаговое руководство. Использование класса join для предотвращения взаимоблокировки.

См. также

Задачи

Пошаговое руководство. Использование класса join для предотвращения взаимоблокировки

Основные понятия

Библиотека асинхронных агентов

Асинхронные блоки сообщений

Функции передачи сообщений

Структуры данных синхронизации