Návod: Vytvoření aplikace založené na agentovi

Toto téma popisuje, jak vytvořit základní aplikaci založenou na agentech. V tomto názorném postupu můžete vytvořit agenta, který čte data z textového souboru asynchronně. Aplikace používá algoritmus kontrolního součtu Adler-32 k výpočtu kontrolního součtu obsahu tohoto souboru.

Požadavky

Abyste mohli tento návod dokončit, musíte porozumět následujícím tématům:

Oddíly

Tento názorný postup ukazuje, jak provádět následující úlohy:

Vytvoření konzolové aplikace

Tato část ukazuje, jak vytvořit konzolovou aplikaci jazyka C++, která odkazuje na soubory hlaviček, které bude program používat. Počáteční kroky se liší v závislosti na tom, jakou verzi sady Visual Studio používáte. Pokud chcete zobrazit dokumentaci pro upřednostňovanou verzi sady Visual Studio, použijte ovládací prvek selektoru verzí . Nachází se v horní části obsahu na této stránce.

Vytvoření konzolové aplikace jazyka C++ v sadě Visual Studio

  1. V hlavní nabídce zvolte Soubor>nový>projekt a otevřete dialogové okno Vytvořit nový projekt.

  2. V horní části dialogového okna nastavte jazyk na C++, nastavte platformu pro Windows a nastavte typ projektu na konzolu.

  3. V filtrovaném seznamu typů projektů zvolte Konzolová aplikace a pak zvolte Další. Na další stránce zadejte BasicAgent jako název projektu a v případě potřeby zadejte umístění projektu.

  4. Zvolte tlačítko Vytvořit a vytvořte projekt.

  5. Pravým tlačítkem myši klikněte na uzel projektu v Průzkumník řešení a zvolte Vlastnosti. V části Vlastnosti>konfigurace C/C++>Předkompilované hlavičky předkompilované hlavičky> zvolte Vytvořit.

Vytvoření konzolové aplikace C++ v sadě Visual Studio 2017 a starších verzích

  1. V nabídce Soubor klepněte na tlačítko Nový a klepněte na tlačítko Projekt zobrazte dialogové okno Nový projekt.

  2. V dialogovém okně Nový projekt vyberte uzel Visual C++ v podokně Typy projektů a pak v podokně Šablony vyberte konzolovou aplikaci Win32. Zadejte název projektu, například BasicAgenta klepněte na tlačítko OK zobrazte Průvodce konzolovou aplikací Win32.

  3. V dialogovém okně Průvodce konzolovou aplikací Win32 klepněte na tlačítko Dokončit.

Aktualizace souboru záhlaví

Do souboru pch.h (stdafx.h v sadě Visual Studio 2017 a starší) přidejte následující kód:

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

Agenti souborů hlaviček.h obsahují funkce třídy concurrency::agent .

Ověření aplikace

Nakonec ověřte, že se aplikace úspěšně vytvořila, a to tak, že ji sestavíte a spustíte. Aplikaci sestavíte tak, že v nabídce Sestavení kliknete na Sestavit řešení. Pokud se aplikace úspěšně sestaví, spusťte aplikaci kliknutím na Spustit ladění v nabídce Ladění .

[Nahoře]

Vytvoření třídy file_reader

Tato část ukazuje, jak vytvořit file_reader třídu. Modul runtime naplánuje, aby každý agent prováděl práci v vlastním kontextu. Proto můžete vytvořit agenta, který provádí synchronně práci, ale interaguje s ostatními komponentami asynchronně. Třída file_reader čte data z daného vstupního souboru a odesílá data z daného souboru do dané cílové komponenty.

Vytvoření třídy file_reader

  1. Přidejte do projektu nový soubor hlaviček jazyka C++. Uděláte to tak, že v Průzkumník řešení kliknete pravým tlačítkem myši na uzel Soubory záhlaví, kliknete na Přidat a potom kliknete na Tlačítko Nová položka. V podokně Šablony vyberte Soubor záhlaví (.h). V dialogovém okně Přidat novou položku zadejte file_reader.h do pole Název a klepněte na tlačítko Přidat.

  2. Do file_reader.h přidejte následující kód.

    #pragma once
    
  3. V file_reader.h vytvořte třídu s názvem file_reader , která je odvozena od agent.

    class file_reader : public concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. Do oddílu předmětu private přidejte následující datové členy.

    std::string _file_name;
    concurrency::ITarget<std::string>& _target;
    concurrency::overwrite_buffer<std::exception> _error;
    

    Člen _file_name je název souboru, ze kterého agent čte. Člen _target je souběžnost::ITarget objekt, do kterého agent zapíše obsah souboru. Člen _error obsahuje všechny chyby, ke kterým dochází během životnosti agenta.

  5. Do části file_reader třídy přidejte následující kód pro file_reader konstruktorypublic.

    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)
    {
    }
    

    Každé přetížení konstruktoru file_reader nastaví datové členy. Druhé a třetí přetížení konstruktoru umožňuje vaší aplikaci používat konkrétní plánovač s vaším agentem. První přetížení používá výchozí plánovač s vaším agentem.

  6. Přidejte metodu get_error do veřejné části file_reader třídy.

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

    Metoda get_error načte všechny chyby, ke kterým dochází během životnosti agenta.

  7. Implementujte metodu concurrency::agent::run v protected části třídy.

    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();
    }
    

Metoda run otevře soubor a načte z něj data. Metoda run používá zpracování výjimek k zachycení všech chyb, ke kterým dochází během zpracování souboru.

Pokaždé, když tato metoda načte data ze souboru, volá funkci concurrency::asend , která tato data odešle do cílové vyrovnávací paměti. Odešle prázdný řetězec do cílové vyrovnávací paměti, aby indikovala konec zpracování.

Následující příklad ukazuje úplný obsah 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;
};

[Nahoře]

Použití třídy file_reader v aplikaci

Tato část ukazuje, jak pomocí file_reader třídy číst obsah textového souboru. Ukazuje také, jak vytvořit objekt concurrency::call , který přijímá data tohoto souboru a vypočítá jeho kontrolní součet Adler-32.

Použití třídy file_reader v aplikaci

  1. Do souboru BasicAgent.cpp přidejte následující #include příkaz.

    #include "file_reader.h"
    
  2. Do souboru BasicAgent.cpp přidejte následující using direktivy.

    using namespace concurrency;
    using namespace std;
    
  3. _tmain Ve funkci vytvořte objekt concurrency::event, který signalizuje ukončení zpracování.

    event e;
    
  4. Vytvořte call objekt, který aktualizuje kontrolní součet při příjmu dat.

    // 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;
       });
    });
    

    Tento call objekt také nastaví event objekt, když obdrží prázdný řetězec, aby signalizoval konec zpracování.

  5. Vytvořte file_reader objekt, který čte ze souboru test.txt a zapíše obsah tohoto souboru do objektu call .

    file_reader reader("test.txt", calculate_checksum);
    
  6. Spusťte agenta a počkejte, až se dokončí.

    reader.start();
    agent::wait(&reader);
    
  7. Počkejte, call až objekt přijme všechna data a dokončí se.

    e.wait();
    
  8. Zkontrolujte chyby v čtečce souborů. Pokud nedošlo k žádné chybě, vypočítejte konečný součet Adler-32 a vytiskněte součet do konzoly.

    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;
    }
    

Následující příklad ukazuje kompletní soubor 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;
   }
}

[Nahoře]

Ukázkový vstup

Toto je ukázkový obsah vstupního souboru text.txt:

The quick brown fox
jumps
over the lazy dog

Ukázkový výstup

Při použití s ukázkovým vstupem vytvoří tento program následující výstup:

Adler-32 sum is fefb0d75

Robustní programování

Pokud chcete zabránit souběžnému přístupu k datovým členům, doporučujeme přidat metody, které provádějí práci do protectedprivate oddílu třídy. Do oddílu třídy přidejte metody, které odesílají nebo přijímají zprávy do nebo z agenta public .

Vždy volejte metodu concurrency::agent::d one a přesuňte agenta do dokončeného stavu. Tuto metodu obvykle voláte před návratem run z metody.

Další kroky

Další příklad aplikace založené na agentech naleznete v části Návod: Použití spojení k zabránění vzájemnému zablokování.

Viz také

Knihovna asynchronních agentů
Asynchronní bloky zpráv
Funkce pro předávání zpráv
Synchronizační datové struktury
Návod: Použití metody join k zabránění vzájemnému zablokování