逐步解說:建立代理程式架構應用程式
本主題說明如何建立基本的代理程式架構應用程式。 在這個逐步解說中,您可以建立會以非同步方式讀取文字檔資料的代理程式。 這個應用程式會使用 Adler-32 總和檢查碼演算法來計算該檔案的內容總和檢查碼。
必要條件
您必須了解下列主題,才能完成這個逐步解說:
章節
這個逐步解說示範如何執行下列工作:
建立主控台應用程式
建立 file_reader 類別
在應用程式中使用 file_reader 類別
建立主控台應用程式
本節顯示如何建立 Visual C++ 主控台應用程式,這個應用程式會參考程式要使用的標頭檔。
若要使用 Win32 主控台應用程式精靈建立 Visual C++ 應用程式
按一下 [檔案] 功能表上的 [開新檔案],然後按一下 [專案] 以顯示 [新增專案] 對話方塊。
在 [新增專案] 對話方塊中,選取 [專案類型] 窗格中的 [Visual C++] 節點,然後選取 [範本] 窗格中的 [Win32 主控台應用程式]。 輸入專案的名稱 (例如 BasicAgent),然後按一下 [確定] 以顯示 [Win32 主控台應用程式精靈]。
按一下 [Win32 主控台應用程式精靈] 對話方塊中的 [完成]。
在 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 建構函式的下列程式碼加入至 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 資料成員。 第二個和第三個建構函式多載可讓應用程式對代理程式使用特定排程器。 第一個多載會對代理程式使用預設排程器。
將 get_error 方法加入至 file_reader 類別的 public 區段。
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 類別來讀取文字檔的內容。 其中也顯示如何建立 concurrency::call 物件,這個物件會接收此檔案資料並計算其 Adler-32 總和檢查碼。
若要在應用程式中使用 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 物件。
建立 file_reader 物件,這個物件要會讀取 test.txt 檔案並將該檔案的內容寫入至 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 "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(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 的範例內容:
範例輸出
與範例輸入搭配使用時,這個程式會產生下列輸出:
健全的程式設計
若要防止對資料成員的並行存取,建議您將實際執行工作的方法加入至類別的 protected 或 private 區段。 請只將會對代理程式傳送或接收訊息的方法加入至類別的 public 區段。
永遠呼叫 concurrency::agent::done 方法讓代理程式變成已完成狀態。 您通常會先呼叫這個方法,再從 run 方法返回。
後續步驟
如需代理程式架構應用程式的另一個範例,請參閱逐步解說:使用聯結以避免死結。