연습: 에이전트 기반 응용 프로그램 만들기

이 항목에서는 에이전트를 기반으로 한 기본 응용 프로그램을 만드는 방법에 대해 설명합니다. 이 연습을 통해 텍스트 파일에서 비동기적으로 데이터를 읽는 에이전트를 만들 수 있습니다. 응용 프로그램에서는 Adler-32 체크섬 알고리즘을 사용하여 해당 파일 콘텐츠의 체크섬을 계산합니다.

사전 요구 사항

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

단원

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

  • 콘솔 응용 프로그램 만들기

  • file_reader 클래스 만들기

  • 응용 프로그램에서 file_reader 클래스 사용

콘솔 응용 프로그램 만들기

이 단원에서는 프로그램에서 사용할 헤더 파일을 참조하는 Visual C++ 콘솔 응용 프로그램을 만드는 방법을 보여 줍니다.

Win32 콘솔 응용 프로그램 마법사를 사용하여 Visual C++ 응용 프로그램을 만들려면

  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. 프로젝트에 새 C++ 헤더 파일을 추가합니다. 이렇게 하려면 솔루션 탐색기에서 헤더 파일 노드를 마우스 오른쪽 단추로 클릭하고 추가를 클릭한 다음, 새 항목을 클릭합니다. 그런 다음 템플릿 창에서 **헤더 파일 (.h)**을 선택합니다. 새 항목 추가 대화 상자가 나타나면 이름 상자에 file_reader.h를 입력하고 추가를 클릭합니다.

  2. file_reader.h에서 다음 코드를 추가합니다.

    #pragma once
    
  3. file_reader.h에 agent에서 파생되는 file_reader 클래스를 만듭니다.

    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. file_reader 생성자에 대한 다음 코드를 file_reader 클래스의 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)
    {
    }
    

    각 생성자 오버로드는 file_reader 데이터 멤버를 설정합니다. 두 번째 및 세 번째 생성자 오버로드를 통해 응용 프로그램에서 특정 스케줄러를 에이전트에 사용할 수 있습니다. 첫 번째 오버로드는 에이전트에 기본 스케줄러를 사용합니다.

  6. get_error 메서드를 file_reader 클래스의 public 섹션에 추가합니다.

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

    get_error 메서드는 에이전트의 수명 동안 발생하는 모든 오류를 검색합니다.

  7. 클래스의 protected 섹션에서 Concurrency::agent::run 메서드를 구현합니다.

    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 클래스를 사용하여 텍스트 파일의 내용을 읽는 방법을 보여 줍니다. 또한 이 파일 데이터를 받고 Adler-32 체크섬을 계산하는 Concurrency::call 개체를 만드는 방법도 보여 줍니다.

응용 프로그램에서 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. test.txt 파일에서 읽고 이 파일의 내용을 call 개체에 쓰는 file_reader 개체를 만듭니다.

    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을 사용하여 교착 상태 방지

개념

비동기 에이전트 라이브러리

비동기 메시지 블록

메시지 전달 함수

동기화 데이터 구조