Поделиться через


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

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

Необходимые компоненты

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

Разделы

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

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

В этом разделе показано, как создать консольное приложение C++, которое ссылается на файлы заголовков, которые будет использоваться программой. Начальные шаги зависят от используемой версии Visual Studio. Чтобы ознакомиться с документацией по предпочтительной версии Visual Studio, используйте селектор Версия. Он находится в верхней части оглавления на этой странице.

Создание консольного приложения C++ в Visual Studio

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

  2. В верхней части диалогового окна задайте для параметра Язык значение C++, для параметра Платформа значение Windows, а для Типа проекта — Консоль.

  3. В отфильтрованном списке типов проектов щелкните Консольное приложение, а затем нажмите кнопку Далее. На следующей странице введите BasicAgent имя проекта и укажите расположение проекта при необходимости.

  4. Нажмите кнопку Создать, чтобы создать проект.

  5. Щелкните правой кнопкой мыши узел проекта в Обозреватель решений и выберите пункт "Свойства". В разделе свойств>конфигурации C/C++>Precompiled Headers Precompiled headers (Предварительно скомпилированные заголовки>) нажмите кнопку Create (Создать).

Создание консольного приложения C++ в Visual Studio 2017 и более ранних версиях

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

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

  3. В диалоговом окне мастера консольного приложения 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

  1. Добавьте новый файл заголовка C++ в проект. Для этого щелкните правой кнопкой мыши узел "Файлы заголовков" в Обозреватель решений, нажмите кнопку "Добавить" и выберите пункт "Создать элемент". В области "Шаблоны" выберите файл заголовка (.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 является объектом параллелизма::ITarget , в который агент записывает содержимое файла. Член _error содержит любую ошибку, возникающую во время существования агента.

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

  6. Добавьте метод в get_error общедоступный раздел file_reader класса.

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

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

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

  1. В BasicAgent.cpp добавьте следующую #include инструкцию.

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

    using namespace concurrency;
    using namespace std;
    
  3. _tmain В функции создайте объект параллелизма::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(begin(s), end(s), [&] (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 "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 для предотвращения взаимоблокировки