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.

Vorbereitungsmaßnahmen

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

  • Erstellen der file_reader-Klasse

  • Verwenden der file_reader-Klasse in der Anwendung

Erstellen der Konsolenanwendung

In diesem Abschnitt wird die Erstellung einer Visual C++-Konsolenanwendung beschrieben, die auf die vom Programm verwendeten Headerdateien verweist.

So erstellen Sie eine Visual C++-Anwendung mit dem Win32-Anwendungs-Assistenten

  1. Klicken Sie im Menü Datei auf Neu, und klicken Sie dann auf Projekt, um das Dialogfeld Neues Projekt anzuzeigen.

  2. Wählen Sie im Dialogfeld Neues Projekt im Bereich Projekttypen den Knoten Visual C++ aus, und wählen Sie dann im Bereich Vorlagen die Option Win32-Konsolenanwendung aus. Geben Sie einen Namen für das Projekt ein, z. B. BasicAgent, und klicken Sie dann auf OK, um den Win32-Anwendungs-Assistenten anzuzeigen.

  3. Klicken Sie im Dialogfeld Win32-Anwendungs-Assistent auf Fertig stellen.

  4. Fügen Sie in der Datei stdafx.h den folgenden Code hinzu.

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

    Die Headerdatei agents.h enthält die Funktionen der Concurrency::agent-Klasse.

  5. Überprüfen Sie, ob die Anwendung erfolgreich erstellt wurde, indem Sie sie erstellen und ausführen. Klicken Sie im Menü Erstellen auf Projektmappe erstellen, um die Anwendung zu erstellen. Wenn die Anwendung erfolgreich erstellt wird, führen Sie die Anwendung aus, indem Sie im Menü Debuggen auf Debugging starten klicken.

[Nach oben]

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

  1. Fügen Sie dem Projekt eine neue C++-Headerdatei hinzu. Klicken Sie hierzu im Projektmappen-Explorer mit der rechten Maustaste auf den Knoten Headerdateien, und klicken Sie auf Hinzufügen und dann auf Neues Element. Wählen Sie im Bereich Vorlagen die Option Headerdatei (.h) aus. Geben Sie im Dialogfeld Neues Element hinzufügen im Feld Name den Namen file_reader.h ein, und klicken Sie auf Hinzufügen.

  2. Fügen Sie in der Datei file_reader.h den folgenden Code hinzu.

    #pragma once
    
  3. Erstellen Sie in der Datei file_reader.h eine Klasse mit dem Namen file_reader, die von agent abgeleitet wird.

    class file_reader : public Concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. 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. Der _target-Member ist ein Concurrency::ITarget-Objekt, in das der Agent den Inhalt der Datei schreibt. Der _error-Member speichert alle Fehler, die während der Lebensdauer des Agents auftreten.

  5. Fügen Sie dem public-Abschnitt der file_reader-Klasse den folgenden Code für die file_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.

  6. Fügen Sie dem public-Abschnitt der file_reader-Klasse die get_error-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.

  7. Implementieren Sie die Concurrency::agent::run-Methode im protected-Abschnitt der 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 ausliest, ruft sie die Concurrency::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;
};

[Nach oben]

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. Darüber hinaus wird auch die Erstellung des Concurrency::call-Objekts beschrieben, das diese Dateidaten empfängt und die Adler-32-Prüfsumme berechnet.

So verwenden Sie die file_reader-Klasse in der Anwendung

  1. Fügen Sie in der Datei BasicAgent.cpp die folgende #include-Anweisung hinzu.

    #include "file_reader.h"
    
  2. Fügen Sie in der Datei BasicAgent.cpp die folgenden using-Direktiven hinzu.

    using namespace Concurrency;
    using namespace std;
    
  3. Erstellen Sie in der _tmain-Funktion ein Concurrency::event-Objekt, das das Ende der Verarbeitung signalisiert.

    event e;
    
  4. 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(s.begin(), s.end(), [&] (char c) {
          a = (a + c) % 65521;
          b = (b + a) % 65521;
       });
    });
    

    Dieses call-Objekt legt darüber hinaus auch das event-Objekt fest, wenn es die leere Zeichenfolge empfängt, um das Ende der Verarbeitung zu signalisieren.

  5. Erstellen Sie ein file_reader-Objekt, das aus der Datei test.txt ausliest und den Inhalt dieser Datei in das call-Objekt schreibt.

    file_reader reader("test.txt", calculate_checksum);
    
  6. Starten Sie den Agent, und warten Sie, bis er beendet wird.

    reader.start();
    agent::wait(&reader);
    
  7. Warten Sie, bis das call-Objekt alle Daten empfangen hat, beenden Sie den Agent.

    e.wait();
    
  8. Ü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 "stdafx.h"
#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(s.begin(), s.end(), [&] (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;
   }
}

[Nach oben]

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 die Concurrency::agent::done-Methode immer auf, wenn der Agent in den abgeschlossenen Zustand verschoben werden soll. 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 Exemplarische Vorgehensweise: Verhindern von Deadlocks mit join.

Siehe auch

Aufgaben

Exemplarische Vorgehensweise: Verhindern von Deadlocks mit join

Konzepte

Asynchronous Agents Library

Asynchrone Nachrichtenblöcke

Funktionen zum Übergeben von Meldungen

Synchronisierungsdatenstrukturen