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
W menu głównym wybierz pozycję Plik>nowy>projekt, aby otworzyć okno dialogowe Tworzenie nowego projektu.
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).
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.Wybierz przycisk Utwórz, aby utworzyć projekt.
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
W menu Plik kliknij pozycję Nowy, a następnie kliknij pozycję Projekt, aby wyświetlić okno dialogowe Nowy projekt.
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,
BasicAgent
aby wyświetlić Kreatora aplikacji konsolowej Win32.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
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.W pliku file_reader.h dodaj następujący kod.
#pragma once
W pliku file_reader.h utwórz klasę o nazwie
file_reader
, która pochodzi z klasyagent
.class file_reader : public concurrency::agent { public: protected: private: };
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.Dodaj następujący kod konstruktorów
file_reader
dopublic
sekcjifile_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.Dodaj metodę
get_error
do sekcji publicznejfile_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.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
W BasicAgent.cpp dodaj następującą
#include
instrukcję.#include "file_reader.h"
W BasicAgent.cpp dodaj następujące
using
dyrektywy.using namespace concurrency; using namespace std;
_tmain
W funkcji utwórz obiekt concurrency::event, który sygnalizuje koniec przetwarzania.event e;
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 ustawiaevent
również obiekt, gdy odbiera pusty ciąg, aby zasygnalizować koniec przetwarzania.file_reader
Utwórz obiekt, który odczytuje z pliku test.txt i zapisuje zawartość tego pliku wcall
obiekcie.file_reader reader("test.txt", calculate_checksum);
Uruchom agenta i poczekaj na jego zakończenie.
reader.start(); agent::wait(&reader);
Poczekaj
call
na odebranie wszystkich danych i zakończenie obiektu.e.wait();
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