チュートリアル: エージェント ベースのアプリケーションの作成
ここでは、基本的なエージェント ベースのアプリケーションの作成方法について説明します。 このチュートリアルでは、テキスト ファイルから非同期的にデータを読み取るエージェントを作成できます。 このアプリケーションでは、Adler-32 チェックサム アルゴリズムを使用して、そのファイルの内容のチェックサムを計算します。
前提条件
このチュートリアルを完了するには、次のトピックを理解する必要があります。
セクション
このチュートリアルでは、次のタスクを実行する方法を示します。
コンソール アプリケーションの作成
ここでは、プログラムで使用されるヘッダー ファイルを参照する C++ コンソール アプリケーションの作成方法について説明します。 最初の手順は、使っている Visual Studio のバージョンによって異なります。 優先するバージョンの Visual Studio のドキュメントを表示するには、 [バージョン] セレクター コントロールを使用します。 このページの目次の一番上にあります。
C++ コンソール アプリケーションを Visual Studio で作成するには
メイン メニューで、[ファイル]>[新規作成]>[プロジェクト] の順に選択して、[新しいプロジェクトの作成] ダイアログ ボックスを開きます。
ダイアログの上部で、[言語] を [C++] に、[プラットフォーム] を [Windows] に、[プロジェクト タイプ] を [コンソール] に設定します。
フィルター処理されたプロジェクト タイプの一覧から、 [コンソール アプリ] を選択して、 [次へ] を選択します。 次のページで、プロジェクトの名前として
BasicAgent
を入力し、必要な場合はプロジェクトの場所を指定します。[作成] ボタンをクリックしてプロジェクトを作成します。
ソリューション エクスプローラーでプロジェクト ノードを右クリックし、[プロパティ] を選択します。 [構成プロパティ]>[C/C++]> [プリコンパイル済みヘッダー]> [プリコンパイル済みヘッダー] で、[作成] を選択します。
C++ コンソール アプリケーションを Visual Studio 2017 以前で作成するには
[ファイル] メニューの [新規作成] をクリックし、[プロジェクト] をクリックして [新しいプロジェクト] ダイアログ ボックスを表示します。
[新しいプロジェクト] ダイアログ ボックスで、[プロジェクトの種類] ペインの [Visual C++] ノードをクリックし、[テンプレート] ペインの [Win32 コンソール アプリケーション] をクリックします。 プロジェクトの名前 (
BasicAgent
など) を入力し、[OK] をクリックして、Win32 コンソール アプリケーション ウィザードを表示します。[Win32 コンソール アプリケーション ウィザード] ダイアログ ボックスで、[完了] をクリックします。
ヘッダー ファイルを更新する
pch.h (Visual Studio 2017 以前の場合は stdafx.h) で、次の行を追加します:
#include <agents.h>
#include <string>
#include <iostream>
#include <algorithm>
agents.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
メンバーは、エージェントがファイルの内容を書き込む concurrency::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
データ メンバーが設定されます。 2 番目と 3 番目のコンストラクター オーバーロードによって、アプリケーションでエージェントに対して特定のスケジューラを使用できるようにします。 最初のオーバーロードでは、エージェントに対して既定のスケジューラを使用します。get_error
クラスのパブリック セクションにfile_reader
メソッドを追加します。bool get_error(std::exception& e) { return try_receive(_error, e); }
get_error
メソッドにより、エージェントの有効期間中に発生したエラーを取得します。クラスの
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 クラスを使用するには
BasicAgent.cpp に、次の
#include
ステートメントを追加します。#include "file_reader.h"
BasicAgent.cpp に、次の
using
ディレクティブを追加します。using namespace concurrency; using namespace std;
_tmain
関数に、処理の終了を通知する concurrency::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
オブジェクトを設定します。test.txt ファイルから読み取り、そのファイルの内容を
file_reader
オブジェクトに書き込む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
セクションには、メッセージをエージェントに送信するメソッドまたはメッセージをエージェントから受信するメソッドのみを追加してください。
必ず、concurrency::agent::done メソッドを呼び出して、エージェントを完了の状態に移行してください。 通常、このメソッドは、run
メソッドから制御が戻る前に呼び出します。
次のステップ
エージェント ベースのアプリケーションのその他の例については、「チュートリアル: 結合を使用したデッドロックの防止」を参照してください。
関連項目
非同期エージェント ライブラリ
非同期メッセージ ブロック
メッセージ パッシング関数
同期データ構造
チュートリアル: join を使用したデッドロックの防止