Dela via


Genomgång: Skapa ett Agent-Based-program

I det här avsnittet beskrivs hur du skapar ett grundläggande agentbaserat program. I den här genomgången kan du skapa en agent som läser data från en textfil asynkront. Programmet använder algoritmen Adler-32 checksum för att beräkna kontrollsumman för innehållet i filen.

Förutsättningar

Du måste förstå följande avsnitt för att slutföra den här genomgången:

Sektioner

Den här genomgången visar hur du utför följande uppgifter:

Skapa konsolprogrammet

Det här avsnittet visar hur du skapar ett C++-konsolprogram som refererar till huvudfilerna som programmet ska använda. De första stegen varierar beroende på vilken version av Visual Studio du använder. Om du vill se dokumentationen för din föredragna version av Visual Studio använder du väljarkontrollen Version. Den finns överst i innehållsförteckningen på den här sidan.

Skapa ett C++-konsolprogram i Visual Studio

  1. På huvudmenyn väljer du Arkiv>Nytt>projekt för att öppna dialogrutan Skapa ett nytt projekt .

  2. Längst upp i dialogrutan anger du Språk till C++, anger Plattform till Windows och anger Projekttyp till Konsol.

  3. I den filtrerade listan över projekttyper väljer du Konsolapp och sedan Nästa. På nästa sida anger du BasicAgent som namn på projektet och anger projektplatsen om du vill.

  4. Välj knappen Skapa för att skapa projektet.

  5. Högerklicka på projektnoden i Solution Explorer och välj Egenskaper. Under Konfigurationsegenskaper>C/C++>Förkompilerade rubriker>Förkompilerat sidhuvud väljer du Skapa.

Skapa ett C++-konsolprogram i Visual Studio 2017 och tidigare

  1. Arkiv-menyn klickar du på Nytt och sedan på Projekt för att visa dialogrutan Nytt projekt .

  2. I dialogrutan Nytt projekt väljer du noden Visual C++ i fönstret Projekttyper och väljer sedan Win32-konsolprogram i fönstret Mallar . Skriv ett namn för projektet, till exempel BasicAgent, och klicka sedan på OK för att visa guiden Win32-konsolprogram.

  3. I dialogrutan Programguide för Win32-konsol klickar du på Slutför.

Uppdatera rubrikfilen

Lägg till följande kod i filen pch.h (stdafx.h i Visual Studio 2017 och tidigare):

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

Huvudfilen agents.h innehåller funktionerna i klassen concurrency::agent .

Verifiera programmet

Kontrollera slutligen att programmet har skapats framgångsrikt genom att bygga och köra det. Om du vill skapa programmet går du till menyn Skapa och klickar på Skapa lösning. Om programmet har skapats framgångsrikt, kör du programmet genom att klicka på Starta felsökningfelsökningsmenyn.

[Topp]

Skapa file_reader-klassen

Det här avsnittet visar hur du skapar file_reader-klassen. Körningsmiljön schemalägger varje agent för att utföra arbete i sin egen kontext. Därför kan du skapa en agent som utför arbetet synkront, men som interagerar med andra komponenter asynkront. Klassen file_reader läser data från en angiven indatafil och skickar data från den filen till en viss målkomponent.

Skapa klassen file_reader

  1. Lägg till en ny C++-huvudfil i projektet. Det gör du genom att högerklicka på noden Rubrikfiler i Solution Explorer, klicka på Lägg till och sedan på Nytt objekt. I fönstret Mallar väljer du Rubrikfil (.h). I dialogrutan Lägg till nytt objekt skriver du file_reader.h i rutan Namn och klickar sedan på Lägg till.

  2. Lägg till följande kod i file_reader.h.

    #pragma once
    
  3. I file_reader.h skapar du en klass med namnet file_reader som härleds från agent.

    class file_reader : public concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. Lägg till följande datamedlemmar i private-avsnittet i din klass.

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

    Elementet _file_name är det filnamn som agenten läser från. Medlemmen _target är ett concurrency::ITarget-objekt till vilket agenten skriver innehållet i filen. Medlemmen _error innehåller eventuella fel som inträffar under agentens livstid.

  5. Lägg till följande kod för file_reader konstruktorerna i public avsnittet i file_reader klassen.

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

    Varje konstruktoröverlagring anger file_reader datamedlemmarna. Med den andra och tredje konstruktorns överlagring kan ditt program använda en specifik schemaläggare med din agent. Den första överlagringen använder standardschemaläggaren med din agent.

  6. Lägg till metoden get_error i den offentliga delen av klassen file_reader.

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

    Metoden get_error hämtar eventuella fel som inträffar under agentens livslängd.

  7. Implementera metoden concurrency::agent::run i protected avsnittet i klassen.

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

Metoden run öppnar filen och läser data från den. Metoden run använder undantagshantering för att samla in eventuella fel som inträffar under filbearbetningen.

Varje gång den här metoden läser data från filen anropas funktionen concurrency::asend för att skicka dessa data till målbufferten. Den skickar den tomma strängen till målbufferten för att indikera slutet på bearbetningen.

I följande exempel visas det fullständiga innehållet i 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;
};

[Topp]

Använda file_reader-klassen i programmet

Det här avsnittet visar hur du använder file_reader klassen för att läsa innehållet i en textfil. Den visar också hur du skapar ett concurrency::call-objekt som tar emot fildata och beräknar Adler-32-kontrollsumman.

Så här använder du klassen file_reader i ditt program

  1. Lägg till följande #include instruktion i BasicAgent.cpp.

    #include "file_reader.h"
    
  2. Lägg till följande using direktiv i BasicAgent.cpp.

    using namespace concurrency;
    using namespace std;
    
  3. _tmain I funktionen skapar du ett samtidighet::händelseobjekt som signalerar slutet på bearbetningen.

    event e;
    
  4. Skapa ett call objekt som uppdaterar kontrollsumman när den tar emot data.

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

    Det här call objektet anger även event objektet när det tar emot den tomma strängen för att signalera slutet av bearbetningen.

  5. Skapa ett file_reader objekt som läser från filen test.txt och skriver innehållet i filen till call objektet.

    file_reader reader("test.txt", calculate_checksum);
    
  6. Starta agenten och vänta tills den är klar.

    reader.start();
    agent::wait(&reader);
    
  7. Vänta tills objektet call har fått alla data och slutförts.

    e.wait();
    
  8. Kontrollera om det finns fel i filläsaren. Om inget fel uppstod beräknar du den slutliga Adler-32-summan och skriver ut summan till konsolen.

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

I följande exempel visas den fullständiga BasicAgent.cpp filen.

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

[Topp]

Exempel på indata

Det här är exempelinnehållet i indatafilen text.txt:

The quick brown fox
jumps
over the lazy dog

Exempelutdata

När det används med exempelindata genererar det här programmet följande utdata:

Adler-32 sum is fefb0d75

Robust Programmering

För att förhindra samtidig åtkomst till datamedlemmar rekommenderar vi att du lägger till metoder som utför arbete i protected avsnittet eller private i din klass. Lägg bara till metoder som skickar eller tar emot meddelanden till eller från agenten i avsnittet i public klassen.

Anropa alltid samtidighet::agent::done metoden för att flytta agenten till slutfört tillstånd. Du anropar vanligtvis den här metoden innan du kommer tillbaka från run metoden.

Nästa steg

Ett annat exempel på ett agentbaserat program finns i Genomgång: Använda koppling för att förhindra dödläge.

Se även

Asynkront agentbibliotek
Asynkrona meddelandeblock
Funktioner för meddelandeöverföring
Datastrukturer för synkronisering
Genomgång: Använda join för att förhindra dödläge