연습: 에이전트 기반 애플리케이션 만들기

이 항목에서는 기본 에이전트 기반 애플리케이션을 만드는 방법을 설명합니다. 이 연습에서는 텍스트 파일에서 데이터를 비동기적으로 읽는 에이전트를 만들 수 있습니다. 애플리케이션은 Adler-32 검사sum 알고리즘을 사용하여 해당 파일 내용의 검사sum을 계산합니다.

필수 조건

이 연습을 완료하려면 다음 항목을 이해해야 합니다.

섹션

이 연습에서는 다음 작업을 수행하는 방법을 보여 줍니다.

콘솔 애플리케이션 만들기

이 섹션에서는 프로그램에서 사용할 헤더 파일을 참조하는 C++ 콘솔 애플리케이션을 만드는 방법을 보여 줍니다. 초기 단계는 사용 중인 Visual Studio 버전에 따라 달라집니다. 기본 설정된 버전의 Visual Studio에 대한 설명서를 보려면 버전 선택기 컨트롤을 사용하세요. 이 페이지의 목차 맨 위에 있습니다.

Visual Studio에서 C++ 콘솔 애플리케이션을 만들려면

  1. 주 메뉴에서 파일>새로 만들기>프로젝트를 선택하여 새 프로젝트 만들기 대화 상자를 엽니다.

  2. 대화 상자 맨 위에서 언어C++로 설정하고, 플랫폼Windows로 설정하고, 프로젝트 형식콘솔로 설정합니다.

  3. 필터링된 프로젝트 형식 목록에서 콘솔 앱을 선택한 후 다음을 선택합니다. 다음 페이지에서 프로젝트의 이름으로 입력 BasicAgent 하고 원하는 경우 프로젝트 위치를 지정합니다.

  4. 만들기 단추를 선택하여 프로젝트를 만듭니다.

  5. 솔루션 탐색기 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 속성을 선택합니다. 구성 속성>에서 C/C++>미리 컴파일된 헤더>미리 컴파일된 헤더는 만들기를 선택합니다.

Visual Studio 2017 및 이전 버전에서 C++ 콘솔 애플리케이션을 만들려면

  1. [파일] 메뉴에서 [새로 만들기]를 클릭한 다음 [프로젝트]를 클릭하여 [새 프로젝트] 대화 상자를 표시합니다.

  2. 새 프로젝트 대화 상자의 프로젝트 유형 창에서 Visual C++ 노드를 선택한 다음 템플릿 창에서 Win32 콘솔 애플리케이션선택합니다. 예를 들어 BasicAgent프로젝트의 이름을 입력한 다음 확인을 클릭하여 Win32 콘솔 애플리케이션 마법사표시합니다.

  3. Win32 콘솔 애플리케이션 마법사 대화 상자에서 마침을 클릭합니다.

헤더 파일 업데이트

pch.h(Visual Studio 2017 이하의 stdafx.h) 파일에서 다음 코드를 추가합니다.

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

헤더 파일 agents.h에는 동시성::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 사용하여 텍스트 파일의 내용을 읽는 방법을 보여 줍니다. 또한 이 파일 데이터를 수신하고 Adler-32 검사sum을 계산하는 동시성::call 개체를 만드는 방법을 보여 줍니다.

애플리케이션에서 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 데이터를 받을 때 검사sum을 업데이트하는 개체를 만듭니다.

    // 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

강력한 프로그래밍

데이터 멤버에 대한 동시 액세스를 방지하려면 작업을 수행하는 메서드를 클래스의 섹션 또는 private 섹션에 protected 추가하는 것이 좋습니다. 에이전트에서 메시지를 보내거나 받는 메서드만 클래스의 섹션에 public 추가합니다.

항상 동시성::agent::d one 메서드를 호출하여 에이전트를 완료된 상태로 이동합니다. 일반적으로 메서드에서 반환하기 전에 이 메서드를 호출합니다 run .

다음 단계

에이전트 기반 애플리케이션의 또 다른 예는 연습: 조인을 사용하여 교착 상태 방지를 참조하세요.

참고 항목

비동기 에이전트 라이브러리
비동기 메시지 블록
메시지 전달 함수
동기화 데이터 구조
연습: 조인을 사용하여 교착 상태 방지