Condividi tramite


Procedura dettagliata: Creazione di un'applicazione basata sugli agenti

Questo argomento descrive come creare un'applicazione basata su agente di base. 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 Di Adler-32 per calcolare il checksum del contenuto del file.

Prerequisiti

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

Sezioni

Questa procedura dettagliata illustra come eseguire le attività seguenti:

Creazione dell'applicazione console

Questa sezione illustra come creare un'applicazione console C++ che fa riferimento ai file di intestazione che verranno usati dal programma. I passaggi iniziali variano a seconda della versione di Visual Studio in uso. Per visualizzare la documentazione relativa alla versione preferita di Visual Studio, usare il controllo selettore della versione . Si trova nella parte superiore del sommario in questa pagina.

Per creare un'applicazione console C++ in Visual Studio

  1. Dal menu principale scegliere File>Nuovo>Progetto per aprire la finestra di dialogo Crea nuovo progetto.

  2. Nella parte superiore della finestra di dialogo impostare Linguaggio su C++ , impostare Piattaforma su Windows e impostare Tipo di progetto su Console.

  3. Nell'elenco filtrato dei tipi di progetto scegliere App console e quindi scegliere Avanti. Nella pagina successiva immettere BasicAgent come nome per il progetto e specificare il percorso del progetto, se necessario.

  4. Scegliere il pulsante Crea per creare il progetto.

  5. Fare clic con il pulsante destro del mouse sul nodo del progetto in Esplora soluzioni e scegliere Proprietà. In Proprietà>di configurazione C/C++>Intestazioni precompilate intestazioni>precompilate scegliere Crea.

Per creare un'applicazione console C++ in Visual Studio 2017 e versioni precedenti

  1. Scegliere Nuovo dal menu File e quindi fare clic su Progetto per visualizzare la finestra di dialogo Nuovo progetto.

  2. Nella finestra di dialogo Nuovo progetto selezionare il nodo Visual C++ nel riquadro Tipi di progetto e quindi selezionare Applicazione console Win32 nel riquadro Modelli. Digitare un nome per il progetto, ad esempio , BasicAgente quindi fare 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.

Aggiornare il file di intestazione

Nel file pch.h (stdafx.h in Visual Studio 2017 e versioni precedenti) aggiungere il codice seguente:

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

Il file di intestazione agents.h contiene la funzionalità della classe concurrency::agent .

Verificare l'applicazione

Infine, verificare che l'applicazione sia stata creata correttamente compilando ed eseguendola. Per compilare l'applicazione, scegliere Compila soluzione dal menu Compila. Se l'applicazione viene compilata correttamente, eseguire l'applicazione facendo clic su Avvia debug dal menu Debug .

[Torna all'inizio]

Creazione della classe file_reader

Questa sezione illustra come creare la file_reader classe . Il runtime pianifica ogni agente per eseguire operazioni nel proprio contesto. Pertanto, è possibile creare un agente che esegue il lavoro in modo sincrono, ma interagisce in modo asincrono con altri componenti. La file_reader classe legge i dati da un determinato file di input e invia dati da tale file a un determinato componente di destinazione.

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 e 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 e quindi fare clic su 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. Aggiungere i membri dati seguenti alla private sezione della classe.

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

    Il _file_name membro è il nome di file da cui l'agente legge. Il _target membro è un oggetto concurrency::ITarget in cui l'agente scrive il contenuto del file. Il _error membro contiene qualsiasi errore che si verifica durante la durata dell'agente.

  5. Aggiungere il codice seguente per i file_reader costruttori alla public sezione della file_reader classe .

    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 file_reader membri dati. Il secondo e terzo overload del costruttore consente all'applicazione di usare un'utilità di pianificazione specifica con l'agente. Il primo overload usa l'utilità di pianificazione predefinita con l'agente.

  6. Aggiungere il get_error metodo alla sezione pubblica della file_reader classe .

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

    Il get_error metodo recupera qualsiasi errore che si verifica durante la durata dell'agente.

  7. Implementare il metodo concurrency::agent::run nella protected sezione della classe.

    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 run metodo apre il file e legge i dati da esso. Il run metodo usa la gestione delle eccezioni per acquisire eventuali errori che si verificano durante l'elaborazione dei file.

Ogni volta che questo metodo legge i dati dal file, chiama la funzione concurrency::asend per inviare tali dati al buffer di destinazione. Invia la stringa vuota al buffer di destinazione per indicare la fine dell'elaborazione.

L'esempio seguente mostra 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;
};

[Torna all'inizio]

Uso della classe file_reader nell'applicazione

Questa sezione illustra come usare la file_reader classe per leggere il contenuto di un file di testo. Viene inoltre illustrato come creare un oggetto concurrency::call che riceve questi dati di file e calcola il checksum di Adler-32.

Per usare la classe file_reader nell'applicazione

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

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

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

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

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

  5. Creare un file_reader oggetto 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 attendere il completamento.

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

    e.wait();
    
  8. Controllare la presenza di errori nel lettore di file. Se non si è verificato alcun errore, calcolare la somma finale di Wsus-32 e stampare la somma nella 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 "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;
   }
}

[Torna all'inizio]

Input di esempio

Questo è il contenuto di esempio del file di input text.txt:

The quick brown fox
jumps
over the lazy dog

Output di esempio

Se usato con l'input di esempio, questo programma genera l'output seguente:

Adler-32 sum is fefb0d75

Programmazione efficiente

Per impedire l'accesso simultaneo ai membri dati, è consigliabile aggiungere metodi che eseguono operazioni alla protected sezione o private della classe. Aggiungere solo metodi che inviano o ricevono messaggi da o verso l'agente alla public sezione della classe.

Chiamare sempre il metodo concurrency::agent::d one per spostare l'agente nello stato completato. Questo metodo viene in genere chiamato prima di tornare dal run metodo .

Passaggi successivi

Per un altro esempio di applicazione basata su agente, vedere Procedura dettagliata: Uso del join per impedire deadlock.

Vedi anche

Libreria di agenti asincroni
Blocchi dei messaggi asincroni
Funzioni di passaggio dei messaggi
Strutture di dati di sincronizzazione
Procedura dettagliata: uso della classe join per impedire un deadlock