Share via


逐步解說:建立代理程式架構應用程式

本主題描述如何建立以代理程式為基礎的基本應用程式。 在本逐步解說中,您可以建立代理程式,以非同步方式從文字檔讀取資料。 應用程式會使用 Adler-32 總和檢查碼演算法來計算該檔案內容的總和檢查碼。

必要條件

您必須瞭解下列主題,才能完成本逐步解說:

區段

本逐步解說示範如何執行下列工作:

建立主控台應用程式

本節說明如何建立 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 中,建立衍生自 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 代理程式寫入檔案內容的並行::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. 在 類別 的 區段中實作 concurrency::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方法會使用例外狀況處理來擷取檔案處理期間發生的任何錯誤。

每次此方法從檔案讀取資料時,它會呼叫 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 類別來讀取文字檔的內容。 它也會示範如何建立 並行::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;
       });
    });
    

    當物件收到空字串以發出處理結尾的訊號時, event 這個 call 物件也會設定 物件。

  5. 建立物件 file_reader ,從 file 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

穩固程式設計

若要防止平行存取資料成員,建議您將執行工作的方法新增至 類別的 protectedprivate 區段。 只新增方法,以將訊息傳送或接收至您類別的 區段,或從代理程式 public 傳送或接收訊息。

請一律呼叫 concurrency::agent::d one 方法,將您的代理程式移至已完成的狀態。 您通常會在從 run 方法傳回之前呼叫這個方法。

後續步驟

如需代理程式型應用程式的另一個範例,請參閱 逐步解說:使用聯結來防止死結

另請參閱

非同步代理程式程式庫
非同步訊息區
訊息傳遞函式
同步處理資料結構
逐步解說:使用聯結以避免死結