Exemplarische Vorgehensweise: Erstellen einer agentbasierten Anwendung
In diesem Thema wird die Erstellung einer einfachen agentbasierten Anwendung beschrieben. In dieser exemplarischen Vorgehensweise können Sie einen Agent erstellen, der Daten asynchron aus einer Textdatei ausliest. Die Anwendung berechnet die Prüfsumme des Inhalts dieser Datei mithilfe des Adler-32-Prüfsummenalgorithmus.
Voraussetzungen
Zum Durchführen dieser exemplarischen Vorgehensweise sollten Sie die folgenden Themen lesen:
Abschnitte
Mit dieser exemplarischen Vorgehensweise wird die Durchführung der folgenden Aufgaben beschrieben:
Erstellen der Konsolenanwendung
In diesem Abschnitt wird gezeigt, wie Sie eine C++-Konsolenanwendung erstellen, die auf die Headerdateien verweist, die vom Programm verwendet werden. Die ersten Schritte variieren je nachdem, welche Version von Visual Studio Sie verwenden. Um die Dokumentation für Ihre bevorzugte Version von Visual Studio anzuzeigen, verwenden Sie das Auswahlsteuerelement Version. Es befindet sich am Anfang des Inhaltsverzeichnisses auf dieser Seite.
So erstellen Sie eine C++-Konsolenanwendung in Visual Studio
Klicken Sie im Hauptmenü auf Datei>Neu>Projekt, um das Dialogfeld Neues Projekt erstellen zu öffnen.
Legen Sie oben im Dialogfeld die Sprache auf C++, die Plattform auf Windows und den Projekttyp auf Konsole fest.
Wählen Sie aus der gefilterten Projekttypliste Konsolen-App aus, und klicken Sie auf Weiter. Geben Sie
BasicAgent
auf der nächsten Seite den Namen des Projekts ein, und geben Sie bei Bedarf den Projektspeicherort an.Klicken Sie auf die Schaltfläche Erstellen, um das Projekt zu erstellen.
Klicken Sie mit der rechten Maustaste auf den Projektknoten in Projektmappen-Explorer, und wählen Sie "Eigenschaften" aus. Wählen Sie unter konfigurationseigenschaften>C/C++>Precompiled Headers>Precompiled headers precompiled header choose Create.
So erstellen Sie eine C++-Konsolenanwendung in Visual Studio 2017 und einer früheren Version
Klicken Sie im Menü "Datei " auf "Neu" und dann auf "Projekt ", um das Dialogfeld "Neues Projekt " anzuzeigen.
Wählen Sie im Dialogfeld "Neues Projekt" im Bereich "Projekttypen" den Knoten "Visual C++" und dann im Bereich "Vorlagen" die Option "Win32-Konsolenanwendung" aus. Geben Sie einen Namen für das Projekt ein, z. B. , und klicken Sie dann auf "OK",
BasicAgent
um den Win32-Konsolenanwendungs-Assistenten anzuzeigen.Klicken Sie im Dialogfeld "Win32-Konsolenanwendungs-Assistent " auf " Fertig stellen".
Aktualisieren der Headerdatei
Fügen Sie in der Datei pch.h (stdafx.h in Visual Studio 2017 und früher) den folgenden Code hinzu:
#include <agents.h>
#include <string>
#include <iostream>
#include <algorithm>
Die Headerdatei agents.h enthält die Funktionalität der Parallelitätsklasse::agent .
Überprüfen der Anwendung
Überprüfen Sie schließlich, ob die Anwendung erfolgreich erstellt wurde, indem Sie sie erstellen und ausführen. Klicken Sie zum Erstellen der Anwendung im Menü "Erstellen " auf " Projektmappe erstellen". Wenn die Anwendung erfolgreich erstellt wird, führen Sie die Anwendung aus, indem Sie im Menü "Debuggen" auf "Debuggen starten" klicken.
Erstellen der file_reader Klasse
In diesem Abschnitt wird die Erstellung der file_reader
-Klasse beschrieben. Die Runtime plant jeden Agent so, dass er Arbeiten im eigenen Kontext ausführt. Daher können Sie einen Agent erstellen, der Arbeiten synchron ausführt, aber asynchron mit anderen Komponenten interagiert. Die file_reader
-Klasse liest Daten aus einer angegebenen Eingabedatei aus und sendet Daten aus dieser Datei an eine angegebene Zielkomponente.
So erstellen Sie die file_reader-Klasse
Fügen Sie dem Projekt eine neue C++-Headerdatei hinzu. Klicken Sie dazu mit der rechten Maustaste auf den Knoten "Headerdateien" in Projektmappen-Explorer, klicken Sie auf "Hinzufügen", und klicken Sie dann auf "Neues Element". Wählen Sie im Bereich "Vorlagen" die Kopfzeilendatei (H) aus. Geben Sie im Dialogfeld "Neues Element hinzufügen" das Feld "Name" ein, und klicken Sie dann auf "Hinzufügen".
file_reader.h
Fügen Sie in der Datei file_reader.h den folgenden Code hinzu.
#pragma once
Erstellen Sie in der Datei file_reader.h eine Klasse mit dem Namen
file_reader
, die vonagent
abgeleitet wird.class file_reader : public concurrency::agent { public: protected: private: };
Fügen Sie dem
private
-Abschnitt der Klasse die folgenden Datenmember hinzu.std::string _file_name; concurrency::ITarget<std::string>& _target; concurrency::overwrite_buffer<std::exception> _error;
Der
_file_name
-Member ist der Name der Datei, die vom Agent ausgelesen wird. Das_target
Element ist ein Parallelitätsobjekt::ITarget , in das der Agent den Inhalt der Datei schreibt. Der_error
-Member speichert alle Fehler, die während der Lebensdauer des Agents auftreten.Fügen Sie dem
file_reader
-Abschnitt derpublic
-Klasse den folgenden Code für diefile_reader
-Konstruktoren hinzu.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) { }
Mit jeder Konstruktorüberladung werden die
file_reader
-Datenmember festgelegt. Mit der zweiten und dritten Konstruktorüberladung wird es der Anwendung ermöglicht, mit dem Agent einen bestimmten Planer zu verwenden. Bei der ersten Überladung wird der Standardplaner mit dem Agent verwendet.Fügen Sie dem public-Abschnitt der
get_error
-Klasse diefile_reader
-Methode hinzu.bool get_error(std::exception& e) { return try_receive(_error, e); }
Die
get_error
-Methode ruft alle Fehler ab, die während der Lebensdauer des Agents auftreten.Implementieren Sie die Parallelität::agent::run-Methode im
protected
Abschnitt Ihrer Klasse.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(); }
Die run
-Methode öffnet die Datei und liest Daten aus. Die run
-Methode erfasst mithilfe der Ausnahmebehandlung alle Fehler, die während der Dateiverarbeitung auftreten.
Jedes Mal, wenn diese Methode Daten aus der Datei liest, ruft sie die Parallelität::asend-Funktion auf, um diese Daten an den Zielpuffer zu senden. Sie sendet die leere Zeichenfolge an den Zielpuffer, um so das Ende der Verarbeitung anzugeben.
Im folgenden Beispiel wird der vollständige Inhalt der Datei file_reader.h dargestellt.
#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;
};
Verwenden der file_reader Klasse in der Anwendung
In diesem Abschnitt wird beschrieben, wie mithilfe der file_reader
-Klasse der Inhalt einer Textdatei gelesen wird. Außerdem wird gezeigt, wie Sie ein Parallelitätsobjekt::call erstellen, das diese Dateidaten empfängt, und die Adler-32-Prüfsumme berechnet.
So verwenden Sie die file_reader-Klasse in der Anwendung
Fügen Sie in der Datei BasicAgent.cpp die folgende
#include
-Anweisung hinzu.#include "file_reader.h"
Fügen Sie in der Datei BasicAgent.cpp die folgenden
using
-Anweisungen hinzu.using namespace concurrency; using namespace std;
Erstellen Sie in der
_tmain
Funktion ein Parallelitätsobjekt::event , das das Ende der Verarbeitung signalisiert.event e;
Erstellen Sie ein
call
-Objekt, das beim Empfang von Daten die Prüfsumme aktualisiert.// 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; }); });
Dieses
call
-Objekt legt darüber hinaus auch dasevent
-Objekt fest, wenn es die leere Zeichenfolge empfängt, um das Ende der Verarbeitung zu signalisieren.Erstellen Sie ein
file_reader
-Objekt, das aus der Datei test.txt ausliest und den Inhalt dieser Datei in dascall
-Objekt schreibt.file_reader reader("test.txt", calculate_checksum);
Starten Sie den Agent, und warten Sie, bis er beendet wird.
reader.start(); agent::wait(&reader);
Warten Sie, bis das
call
-Objekt alle Daten empfangen hat, beenden Sie den Agent.e.wait();
Überprüfen Sie die file_reader-Klasse auf Fehler. Wenn kein Fehler aufgetreten ist, berechnen Sie die abschließende Adler-32-Prüfsumme, und geben Sie die Summe an der Konsole aus.
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; }
Das folgende Beispiel zeigt die vollständige BasicAgent.cpp-Datei.
// 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;
}
}
Beispieleingabe
Dies ist der Beispielinhalt der Eingabedatei text.txt:
The quick brown fox
jumps
over the lazy dog
Beispielausgabe
Wenn dieses Programm mit der Beispieleingabe verwendet wird, generiert es die folgende Ausgabe:
Adler-32 sum is fefb0d75
Stabile Programmierung
Um gleichzeitigen Zugriff auf Datenmember zu verhindern, wird empfohlen, Methoden hinzufügen, die Arbeiten am protected
-Abschnitt oder am private
-Abschnitt der Klasse durchführen. Fügen Sie dem public
-Abschnitt der Klasse nur Methoden hinzu, die Nachrichten an den Agent senden oder vom Agent empfangen.
Rufen Sie immer die Parallelität::agent::d one-Methode auf, um Ihren Agent in den abgeschlossenen Zustand zu verschieben. Diese Methode wird in der Regel vor der Rückkehr von der run
-Methode aufgerufen.
Nächste Schritte
Ein weiteres Beispiel für eine agentbasierte Anwendung finden Sie unter Walkthrough: Using join to Prevent Deadlock.
Siehe auch
Asynchrone Agents Library
Asynchrone Nachrichtenblöcke
Funktionen zum Übergeben von Nachrichten
Synchronisierungsdatenstrukturen
Exemplarische Vorgehensweise: Verhindern von Deadlocks mit join