Udostępnij za pośrednictwem


Wskazówki: tworzenie aplikacji opartej o agentów

W tym temacie opisano sposób tworzenia podstawowej aplikacji opartej na agencie. W tym przewodniku możesz utworzyć agenta, który odczytuje dane z pliku tekstowego asynchronicznie. Aplikacja oblicza sumę kontrolną zawartości tego pliku przy użyciu algorytmu sumy kontrolnej Adler-32.

Wymagania wstępne

Aby ukończyć ten przewodnik, musisz zapoznać się z następującymi tematami:

Sekcje

W tym przewodniku pokazano, jak wykonywać następujące zadania:

Tworzenie aplikacji konsoli

W tej sekcji pokazano, jak utworzyć aplikację konsolową języka C++, która odwołuje się do plików nagłówkowych używanych przez program. Początkowe kroki różnią się w zależności od używanej wersji programu Visual Studio. Aby zapoznać się z dokumentacją preferowanej wersji programu Visual Studio, użyj kontrolki selektora wersji . Znajduje się on w górnej części spisu treści na tej stronie.

Aby utworzyć aplikację konsolową języka C++ w programie Visual Studio

  1. W menu głównym wybierz pozycję Plik>nowy>projekt, aby otworzyć okno dialogowe Tworzenie nowego projektu.

  2. W górnej części okna dialogowego ustaw wartość Language na C++, ustaw wartość Platforma na Windows, a następnie ustaw wartość Project type (Typ projektu) na Console (Konsola).

  3. Z filtrowanej listy typów projektów wybierz pozycję Aplikacja konsolowa, a następnie wybierz pozycję Dalej. Na następnej stronie wprowadź BasicAgent nazwę projektu i w razie potrzeby określ lokalizację projektu.

  4. Wybierz przycisk Utwórz, aby utworzyć projekt.

  5. Kliknij prawym przyciskiem myszy węzeł projektu w Eksplorator rozwiązań, a następnie wybierz polecenie Właściwości. W obszarze Właściwości>konfiguracji C/C++>Prekompilowane nagłówki>prekompilowane wybierz pozycję Utwórz.

Aby utworzyć aplikację konsolową języka C++ w programie Visual Studio 2017 i starszych wersjach

  1. W menu Plik kliknij pozycję Nowy, a następnie kliknij pozycję Projekt, aby wyświetlić okno dialogowe Nowy projekt.

  2. W oknie dialogowym Nowy projekt wybierz węzeł Visual C++ w okienku Typy projektów, a następnie wybierz pozycję Aplikacja konsolowa Win32 w okienku Szablony. Wpisz nazwę projektu, na przykład , a następnie kliknij przycisk OK, BasicAgentaby wyświetlić Kreatora aplikacji konsolowej Win32.

  3. W Kreatorze aplikacji konsolowej Win32 okno dialogowe, kliknij przycisk Zakończ.

Aktualizowanie pliku nagłówka

W pliku pch.h (stdafx.h w programie Visual Studio 2017 i starszym) dodaj następujący kod:

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

Nagłówkowy plik agents.h zawiera funkcje klasy concurrency::agent .

Weryfikowanie aplikacji

Na koniec sprawdź, czy aplikacja została pomyślnie utworzona, kompilując i uruchamiając ją. Aby skompilować aplikację, w menu Kompilacja kliknij pozycję Kompiluj rozwiązanie. Jeśli aplikacja zostanie pomyślnie skompilowany, uruchom aplikację, klikając pozycję Rozpocznij debugowanie w menu Debugowanie.

[Top]

Tworzenie klasy file_reader

W tej sekcji pokazano, jak utworzyć klasę file_reader . Środowisko uruchomieniowe planuje pracę każdego agenta we własnym kontekście. W związku z tym można utworzyć agenta, który wykonuje pracę synchronicznie, ale współdziała z innymi składnikami asynchronicznie. Klasa file_reader odczytuje dane z danego pliku wejściowego i wysyła dane z tego pliku do danego składnika docelowego.

Aby utworzyć klasę file_reader

  1. Dodaj nowy plik nagłówka języka C++ do projektu. Aby to zrobić, kliknij prawym przyciskiem myszy węzeł Pliki nagłówka w Eksplorator rozwiązań, kliknij przycisk Dodaj, a następnie kliknij pozycję Nowy element. W okienku Szablony wybierz pozycję Plik nagłówka (h). W oknie dialogowym Dodawanie nowego elementu wpisz file_reader.h nazwę, a następnie kliknij przycisk Dodaj.

  2. W pliku file_reader.h dodaj następujący kod.

    #pragma once
    
  3. W pliku file_reader.h utwórz klasę o nazwie file_reader , która pochodzi z klasy agent.

    class file_reader : public concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. Dodaj następujące składowe danych do private sekcji klasy.

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

    Element _file_name członkowski jest nazwą pliku odczytaną przez agenta. Element _target członkowski jest obiektem współbieżności::ITarget , do którego agent zapisuje zawartość pliku. Element _error członkowski zawiera wszelkie błędy występujące w okresie życia agenta.

  5. Dodaj następujący kod konstruktorów file_reader do public sekcji file_reader klasy .

    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żde przeciążenie konstruktora ustawia file_reader elementy członkowskie danych. Drugie i trzecie przeciążenie konstruktora umożliwia aplikacji używanie określonego harmonogramu z agentem. Pierwsze przeciążenie używa domyślnego harmonogramu z agentem.

  6. Dodaj metodę get_error do sekcji publicznej file_reader klasy.

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

    Metoda get_error pobiera wszelkie błędy występujące w okresie życia agenta.

  7. Zaimplementuj metodę concurrency::agent::run w protected sekcji klasy.

    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 otwiera plik i odczytuje z niego dane. Metoda run używa obsługi wyjątków do przechwytywania błędów występujących podczas przetwarzania plików.

Za każdym razem, gdy ta metoda odczytuje dane z pliku, wywołuje funkcję concurrency::asend w celu wysłania tych danych do buforu docelowego. Wysyła pusty ciąg do buforu docelowego, aby wskazać koniec przetwarzania.

Poniższy przykład przedstawia pełną zawartość pliku 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;
};

[Top]

Używanie klasy file_reader w aplikacji

W tej sekcji pokazano, jak używać file_reader klasy do odczytywania zawartości pliku tekstowego. Pokazano również, jak utworzyć obiekt współbieżności::call , który odbiera te dane pliku i oblicza sumę kontrolną Ma-32.

Aby użyć klasy file_reader w aplikacji

  1. W BasicAgent.cpp dodaj następującą #include instrukcję.

    #include "file_reader.h"
    
  2. W BasicAgent.cpp dodaj następujące using dyrektywy.

    using namespace concurrency;
    using namespace std;
    
  3. _tmain W funkcji utwórz obiekt concurrency::event, który sygnalizuje koniec przetwarzania.

    event e;
    
  4. Utwórz obiekt, który aktualizuje sumę kontrolną call po odebraniu danych.

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

    Ten call obiekt ustawia event również obiekt, gdy odbiera pusty ciąg, aby zasygnalizować koniec przetwarzania.

  5. file_reader Utwórz obiekt, który odczytuje z pliku test.txt i zapisuje zawartość tego pliku w call obiekcie.

    file_reader reader("test.txt", calculate_checksum);
    
  6. Uruchom agenta i poczekaj na jego zakończenie.

    reader.start();
    agent::wait(&reader);
    
  7. Poczekaj call na odebranie wszystkich danych i zakończenie obiektu.

    e.wait();
    
  8. Sprawdź czytnik plików pod kątem błędów. Jeśli nie wystąpił błąd, oblicz końcową sumę Adler-32 i wyświetl sumę do konsoli.

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

W poniższym przykładzie przedstawiono kompletny plik 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;
   }
}

[Top]

Przykładowe dane wejściowe

Jest to przykładowa zawartość pliku wejściowego text.txt:

The quick brown fox
jumps
over the lazy dog

Przykładowe dane wyjściowe

W przypadku użycia z przykładowymi danymi wejściowymi ten program generuje następujące dane wyjściowe:

Adler-32 sum is fefb0d75

Niezawodne programowanie

Aby uniemożliwić współbieżny dostęp do składowych danych, zalecamy dodanie metod wykonujących pracę do protected sekcji lub private klasy. Dodaj tylko metody, które wysyłają lub odbierają komunikaty do lub z agenta do public sekcji klasy.

Zawsze wywołaj metodę concurrency::agent::d one , aby przenieść agenta do stanu ukończonego. Zazwyczaj ta metoda jest wywoływana przed zwróceniem run z metody .

Następne kroki

Aby zapoznać się z innym przykładem aplikacji opartej na agencie, zobacz Przewodnik: Używanie sprzężenia do zapobiegania zakleszczeniom.

Zobacz też

Biblioteki agentów asynchronicznych
Bloki komunikatów asynchronicznych
Funkcje przekazywania komunikatów
Struktury danych synchronizacji
Przewodnik: korzystanie ze złączy w celu zapobiegania zakleszczeniom