Procedura dettagliata: creazione di un'applicazione basata sugli agenti

In questo argomento viene descritto come creare un'applicazione di base basata sugli agenti. In questa procedura dettagliata è possibile creare un agente che legge i dati da un file di testo in modo asincrono. L'applicazione utilizza l'algoritmo di checksum Adler-32 per calcolare il checksum del contenuto del file.

Prerequisiti

Per completare questa procedura dettagliata, è necessario comprendere gli argomenti seguenti:

Sezioni

In questa procedura dettagliata viene illustrato come eseguire le attività seguenti:

  • Creazione dell'applicazione console

  • Creazione della classe file_reader

  • Utilizzo della classe file_reader nell'applicazione

Creazione dell'applicazione console

In questa sezione viene illustrato come creare un'applicazione console Visual C++ che fa riferimento ai file di intestazione utilizzati dal programma.

Per creare un'applicazione Visual C++ tramite la Creazione guidata applicazione console Win32

  1. Scegliere Nuovo dal menu File, quindi fare clic su Progetto. Verrà visualizzata la finestra di dialogo Nuovo progetto.

  2. Nella riquadro Tipi progetto della finestra di dialogo Nuovo progetto selezionare il nodo Visual C++, quindi selezionare Progetto console Win32 nel riquadro Modelli. Digitare un nome per il progetto, ad esempio, BasicAgent, quindi fa clic su OK per visualizzare la Creazione guidata applicazione console Win32.

  3. Nella finestra di dialogo Creazione guidata applicazione console Win32 fare clic su Fine.

  4. In stdafx.h aggiungere il codice seguente.

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

    Il file di intestazione agents.h contiene le funzionalità della classe Concurrency::agent.

  5. Verificare che l'applicazione sia stata creata correttamente compilandola ed eseguendola. Per compilare l'applicazione, scegliere Compila soluzione dal menu Compila. Se l'applicazione viene compilata correttamente, eseguirla scegliendo Avvia debug dal menu Debug.

[vai all'inizio]

Creazione della classe file_reader

In questa sezione viene illustrato come creare la classe file_reader. Il runtime pianifica ogni agente per l'esecuzione del lavoro in un contesto specifico. Pertanto, è possibile creare un agente che esegue il lavoro in modo sincrono, ma interagisce con gli altri componenti in modo asincrono. La classe file_reader legge i dati da un file di input specificato e invia i dati del file a un componente di destinazione specificato.

Per creare la classe file_reader

  1. Aggiungere un nuovo file di intestazione C++ al progetto. A tale scopo, fare clic con il pulsante destro del mouse sul nodo File di intestazione in Esplora soluzioni, scegliere Aggiungi, quindi fare clic su Nuovo elemento. Nel riquadro Modelli selezionare File di intestazione (.h). Nella finestra di dialogo Aggiungi nuovo elemento digitare file_reader.h nella casella Nome, quindi scegliere Aggiungi.

  2. In file_reader.h aggiungere il codice seguente.

    #pragma once
    
  3. In file_reader.h creare una classe denominata file_reader che deriva da agent.

    class file_reader : public Concurrency::agent
    {
    public:
    protected:
    private:
    };
    
  4. Nella sezione private della classe aggiungere i membri dati seguenti.

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

    Il membro _file_name è il nome file da cui legge l'agente. Il membro _target è un oggetto Concurrency::ITarget in cui l'agente scrive il contenuto del file. Il membro _error gestisce gli errori che possono verificarsi per tutta la durata dell'agente.

  5. Nella sezione public della classe file_reader aggiungere il codice seguente per il costruttore file_reader.

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

    Ogni overload del costruttore imposta i membri dati file_reader. Il secondo e terzo overload del costruttore consente all'applicazione di utilizzare un'utilità di pianificazione specifica con l'agente. Il primo overload utilizza l'utilità di pianificazione predefinita con l'agente.

  6. Nella sezione public della classe file_reader aggiungere il metodo get_error.

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

    Il metodo get_error recupera gli errori che possono verificarsi per tutta la durata dell'agente.

  7. Nella sezione protected della classe implementare il metodo Concurrency::agent::run.

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

    Il metodo run apre il file da cui vengono letti i dati. Il metodo run utilizza la gestione delle eccezioni per acquisire gli errori che possono verificarsi durante l'elaborazione del file.

    Ogni volta che questo metodo legge i dati dal file, chiama la funzione Concurrency::asend per inviare i dati al buffer di destinazione. Per indicare la fine dell'elaborazione il metodo invia al buffer di destinazione una stringa vuota.

Nell'esempio seguente viene illustrato il contenuto completo di 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;
};

[vai all'inizio]

Utilizzo della classe file_reader nell'applicazione

In questa sezione viene illustrato come utilizzare la classe file_reader per leggere il contenuto di un file di testo. Viene inoltre illustrato come creare un oggetto Concurrency::call che riceve i dati del file e calcola il relativo checksum Adler-32.

Per utilizzare la classe file_reader nell'applicazione

  1. In BasicAgent.cpp aggiungere l'istruzione #include seguente.

    #include "file_reader.h"
    
  2. In BasicAgent.cpp aggiungere le direttive using seguenti.

    using namespace Concurrency;
    using namespace std;
    
  3. Nella funzione _tmain creare un oggetto Concurrency::event che segnala la fine dell'elaborazione.

    event e;
    
  4. Creare un oggetto call che aggiorna il checksum quando riceve i dati.

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

    L'oggetto call imposta inoltre l'oggetto event quando riceve la stringa vuota per segnalare la fine dell'elaborazione.

  5. Creare un oggetto file_reader che legge dal file test.txt e scrive il contenuto del file nell'oggetto call.

    file_reader reader("test.txt", calculate_checksum);
    
  6. Avviare l'agente e attenderne il completamento.

    reader.start();
    agent::wait(&reader);
    
  7. Attendere che l'oggetto call riceva tutti i dati e completi l'esecuzione.

    e.wait();
    
  8. Verificare la presenza di errori nel lettore di file. Se non si è verificato alcun errore, calcolare il checksum Adler-32 finale e visualizzarlo sulla 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;
    }
    

Nell'esempio seguente viene illustrato il file BasicAgent.cpp completo.

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

[vai all'inizio]

Input di esempio

Di seguito viene riportato il contenuto di esempio del file di input text.txt:

The quick brown fox
jumps
over the lazy dog

Output di esempio

Se utilizzato con l'input di esempio, questo programma produce l'output seguente:

Adler-32 sum is fefb0d75

Programmazione robusta

Per impedire l'accesso simultaneo ai membri dati, è consigliabile aggiungere i metodi che eseguono il lavoro nella sezione protected o private della classe. Aggiungere solo i metodi che inviano o ricevono messaggi dall'agente nella sezione public della classe.

Chiamare sempre il metodo Concurrency::agent::done per impostare lo stato completato dell'agente. Questo metodo viene in genere chiamato prima del completamento del metodo run.

Passaggi successivi

Per un altro esempio di un'applicazione basata sugli agenti, vedere Procedura dettagliata: utilizzo della classe join per impedire un deadlock.

Vedere anche

Attività

Procedura dettagliata: utilizzo della classe join per impedire un deadlock

Concetti

Libreria di agenti asincroni

Blocchi dei messaggi asincroni

Funzioni di passaggio dei messaggi

Strutture di dati di sincronizzazione