Condividi tramite


Blocchi dei messaggi asincroni

La libreria agenti fornisce diversi tipi di blocchi di messaggi che consentono di propagare messaggi tra i componenti dell'applicazione in modo thread-safe. Questi tipi di blocco di messaggi vengono spesso usati con le varie routine di passaggio dei messaggi, ad esempio concurrency::send, concurrency::asend, concurrency::receive e concurrency::try_receive. Per altre informazioni sul passaggio di routine definite dalla libreria agenti, vedere Funzioni di passaggio dei messaggi.

Sezioni

Questo argomento include le sezioni seguenti:

Origini e destinazioni

Le origini e le destinazioni sono due importanti partecipanti al passaggio dei messaggi. Un'origine fa riferimento a un endpoint di comunicazione che invia messaggi. Una destinazione fa riferimento a un endpoint di comunicazione che riceve messaggi. È possibile considerare un'origine come un endpoint da cui si legge e una destinazione come endpoint in cui si scrive. Le applicazioni connettono origini e destinazioni insieme per formare reti di messaggistica.

La libreria agenti usa due classi astratte per rappresentare origini e destinazioni: concurrency::ISource e concurrency::ITarget. I tipi di blocco di messaggi che fungono da origini derivano da ISource; tipi di blocchi di messaggi che fungono da destinazioni derivano da ITarget. I tipi di blocco di messaggi che fungono da origini e destinazioni derivano sia da che ITargetda ISource .

[Torna all'inizio]

Propagazione dei messaggi

La propagazione dei messaggi è l'atto di inviare un messaggio da un componente a un altro. Quando viene offerto un messaggio, un blocco di messaggi può accettare, rifiutare o posticipare il messaggio. Ogni tipo di blocco di messaggi archivia e trasmette i messaggi in modi diversi. Ad esempio, la unbounded_buffer classe archivia un numero illimitato di messaggi, la overwrite_buffer classe archivia un singolo messaggio alla volta e la classe transformer archivia una versione modificata di ogni messaggio. Questi tipi di blocchi di messaggi sono descritti in modo più dettagliato più avanti in questo documento.

Quando un blocco di messaggi accetta un messaggio, può facoltativamente eseguire operazioni e, se il blocco di messaggi è un'origine, passare il messaggio risultante a un altro membro della rete. Un blocco di messaggi può usare una funzione di filtro per rifiutare i messaggi che non desidera ricevere. I filtri sono descritti in modo più dettagliato più avanti in questo argomento, nella sezione Filtro messaggi. Un blocco di messaggi che posticipa un messaggio può riservare tale messaggio e utilizzarlo in un secondo momento. La prenotazione dei messaggi viene descritta in modo più dettagliato più avanti in questo argomento, nella sezione Prenotazione messaggi.

La libreria agenti consente ai blocchi di messaggi di passare in modo asincrono o sincrono i messaggi. Quando si passa un messaggio a un blocco di messaggi in modo sincrono, ad esempio usando la send funzione , il runtime blocca il contesto corrente finché il blocco di destinazione non accetta o rifiuta il messaggio. Quando si passa un messaggio a un blocco di messaggi in modo asincrono, ad esempio usando la asend funzione , il runtime offre il messaggio alla destinazione e se la destinazione accetta il messaggio, il runtime pianifica un'attività asincrona che propaga il messaggio al ricevitore. Il runtime usa attività leggere per propagare i messaggi in modo cooperativo. Per altre informazioni sulle attività leggere, vedere Utilità di pianificazione.

Le applicazioni connettono origini e destinazioni insieme per formare reti di messaggistica. In genere, si collega la rete e si chiama send o asend per passare i dati alla rete. Per connettere un blocco di messaggi di origine a una destinazione, chiamare il metodo concurrency::ISource::link_target . Per disconnettere un blocco di origine da una destinazione, chiamare il metodo concurrency::ISource::unlink_target . Per disconnettere un blocco di origine da tutte le destinazioni, chiamare il metodo concurrency::ISource::unlink_targets . Quando uno dei tipi di blocco di messaggi predefiniti lascia l'ambito o viene eliminato definitivamente, si disconnette automaticamente da qualsiasi blocco di destinazione. Alcuni tipi di blocchi di messaggi limitano il numero massimo di destinazioni in cui è possibile scrivere. Nella sezione seguente vengono descritte le restrizioni applicabili ai tipi di blocchi di messaggi predefiniti.

[Torna all'inizio]

Panoramica dei tipi di blocchi di messaggi

La tabella seguente descrive brevemente il ruolo dei tipi importanti di blocchi di messaggi.

unbounded_buffer
Archivia una coda di messaggi.

overwrite_buffer
Archivia un messaggio in cui è possibile scrivere e leggere più volte.

single_assignment
Archivia un messaggio che può essere scritto una sola volta e letto da più volte.

call
Esegue il lavoro quando riceve un messaggio.

trasformatore
Esegue il lavoro quando riceve i dati e invia il risultato di tale operazione a un altro blocco di destinazione. La transformer classe può agire su tipi di input e output diversi.

choice
Seleziona il primo messaggio disponibile da un set di origini.

join e join multitipo
Attendere che tutti i messaggi vengano ricevuti da un set di origini e quindi combinare i messaggi in un messaggio per un altro blocco di messaggi.

temporizzatore
Invia un messaggio a un blocco di destinazione a intervalli regolari.

Questi tipi di blocchi di messaggi presentano caratteristiche diverse che le rendono utili per situazioni diverse. Ecco alcune delle caratteristiche seguenti:

  • Tipo di propagazione: indica se il blocco di messaggi funge da origine di dati, da un ricevitore di dati o da entrambi.

  • Ordinamento dei messaggi: indica se il blocco di messaggi mantiene l'ordine originale in cui i messaggi vengono inviati o ricevuti. Ogni tipo di blocco di messaggi predefinito mantiene l'ordine originale in cui invia o riceve messaggi.

  • Numero di origini: numero massimo di origini da cui il blocco di messaggi può leggere.

  • Numero di destinazioni: numero massimo di destinazioni in cui il blocco di messaggi può scrivere.

Nella tabella seguente viene illustrato il modo in cui queste caratteristiche sono correlate ai vari tipi di blocchi di messaggi.

Tipo di blocco di messaggi Tipo di propagazione (origine, destinazione o entrambi) Ordinamento dei messaggi (ordinato o non ordinato) Conteggio delle origini Conteggio di destinazione
unbounded_buffer Entrambi Quantità ordinata Senza limiti Senza limiti
overwrite_buffer Entrambi Quantità ordinata Senza limiti Senza limiti
single_assignment Entrambi Quantità ordinata Senza limiti Senza limiti
call Destinazione Quantità ordinata Senza limiti Non applicabile
transformer Entrambi Quantità ordinata Senza limiti 1
choice Entrambi Quantità ordinata 10 1
join Entrambi Quantità ordinata Senza limiti 1
multitype_join Entrambi Quantità ordinata 10 1
timer Origine Non applicabile Non applicabile 1

Le sezioni seguenti descrivono i tipi di blocchi di messaggi in modo più dettagliato.

[Torna all'inizio]

Classe unbounded_buffer

La classe concurrency::unbounded_buffer rappresenta una struttura di messaggistica asincrona per utilizzo generico. Questa classe archivia una coda di messaggi FIFO (First In, First Out) che possono essere letti da più destinazioni o in cui possono scrivere più origini. Quando una destinazione riceve un messaggio da un unbounded_buffer oggetto , tale messaggio viene rimosso dalla coda di messaggi. Pertanto, anche se un unbounded_buffer oggetto può avere più destinazioni, ogni messaggio riceverà una sola destinazione. La classe unbounded_buffer è utile quando si vogliono passare più messaggi a un altro componente e tale componente deve ricevere ogni messaggio.

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la unbounded_buffer classe . Questo esempio invia tre valori a un unbounded_buffer oggetto e quindi legge tali valori dallo stesso oggetto.

// unbounded_buffer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create an unbounded_buffer object that works with
   // int data.
   unbounded_buffer<int> items;

   // Send a few items to the unbounded_buffer object.
   send(items, 33);
   send(items, 44);
   send(items, 55);

   // Read the items from the unbounded_buffer object and print
   // them to the console.
   wcout << receive(items) << endl;
   wcout << receive(items) << endl;
   wcout << receive(items) << endl;
}

Nell'esempio viene prodotto l'output seguente:

334455

Per un esempio completo che illustra come usare la unbounded_buffer classe , vedere Procedura: Implementare vari modelli producer-consumer.

[Torna all'inizio]

Classe overwrite_buffer

La classe concurrency::overwrite_buffer è simile alla unbounded_buffer classe , ad eccezione del fatto che un overwrite_buffer oggetto archivia un solo messaggio. Inoltre, quando una destinazione riceve un messaggio da un overwrite_buffer oggetto , tale messaggio non viene rimosso dal buffer. Pertanto, più destinazioni riceveranno una copia del messaggio.

La overwrite_buffer classe è utile quando si desidera passare più messaggi a un altro componente, ma tale componente richiede solo il valore più recente. Questa classe è utile anche quando si vuole trasmettere un messaggio a più componenti.

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la overwrite_buffer classe . Questo esempio invia tre valori a un overwrite _buffer oggetto e quindi legge il valore corrente dallo stesso oggetto tre volte. Questo esempio è simile all'esempio per la unbounded_buffer classe . Tuttavia, la overwrite_buffer classe archivia un solo messaggio. Inoltre, il runtime non rimuove il messaggio da un overwrite_buffer oggetto dopo la lettura.

// overwrite_buffer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create an overwrite_buffer object that works with
   // int data.
   overwrite_buffer<int> item;

   // Send a few items to the overwrite_buffer object.
   send(item, 33);
   send(item, 44);
   send(item, 55);

   // Read the current item from the overwrite_buffer object and print
   // it to the console three times.
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
}

Nell'esempio viene prodotto l'output seguente:

555555

Per un esempio completo che illustra come usare la overwrite_buffer classe , vedere Procedura: Implementare vari modelli producer-consumer.

[Torna all'inizio]

Classe single_assignment

La classe concurrency::single_assignment è simile alla overwrite_buffer classe , ad eccezione del fatto che un single_assignment oggetto può essere scritto una sola volta. Come per la classe overwrite_buffer, quando da una destinazione riceve un messaggio da un oggetto single_assignment, il messaggio viene rimosso dalla coda di messaggi. Pertanto, più destinazioni riceveranno una copia del messaggio. La single_assignment classe è utile quando si vuole trasmettere un messaggio a più componenti.

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la single_assignment classe . Questo esempio invia tre valori a un single_assignment oggetto e quindi legge il valore corrente dallo stesso oggetto tre volte. Questo esempio è simile all'esempio per la overwrite_buffer classe . Anche se entrambe le overwrite_buffer classi e single_assignment archiviano un singolo messaggio, la single_assignment classe può essere scritta una sola volta.

// single_assignment-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create an single_assignment object that works with
   // int data.
   single_assignment<int> item;

   // Send a few items to the single_assignment object.
   send(item, 33);
   send(item, 44);
   send(item, 55);

   // Read the current item from the single_assignment object and print
   // it to the console three times.
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
   wcout << receive(item) << endl;
}

Nell'esempio viene prodotto l'output seguente:

333333

Per un esempio completo che illustra come usare la single_assignment classe , vedere Procedura dettagliata: Implementazione di futures.

[Torna all'inizio]

Classe call

La classe concurrency::call funge da ricevitore di messaggi che esegue una funzione di lavoro quando riceve i dati. Questa funzione di lavoro può essere un'espressione lambda, un oggetto funzione o un puntatore a funzione. Un call oggetto si comporta in modo diverso rispetto a una normale chiamata di funzione perché agisce in parallelo ad altri componenti che inviano messaggi. Se un call oggetto funziona quando riceve un messaggio, aggiunge tale messaggio a una coda. Ogni call oggetto elabora i messaggi in coda nell'ordine in cui vengono ricevuti.

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la call classe . In questo esempio viene creato un call oggetto che stampa ogni valore ricevuto nella console. L'esempio invia quindi tre valori all'oggetto call . Poiché l'oggetto call elabora i messaggi in un thread separato, in questo esempio viene utilizzata anche una variabile contatore e un oggetto evento per garantire che l'oggetto call elabori tutti i messaggi prima che la wmain funzione restituisca.

// call-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // An event that is set when the call object receives all values.
   event received_all;

   // Counts the 
   long receive_count = 0L;
   long max_receive_count = 3L;

   // Create an call object that works with int data.
   call<int> target([&received_all,&receive_count,max_receive_count](int n) {
      // Print the value that the call object receives to the console.
      wcout << n << endl;
      
      // Set the event when all messages have been processed.
      if (++receive_count == max_receive_count)
         received_all.set();
   });

   // Send a few items to the call object.
   send(target, 33);
   send(target, 44);
   send(target, 55);

   // Wait for the call object to process all items.
   received_all.wait();
}

Nell'esempio viene prodotto l'output seguente:

334455

Per un esempio completo che illustra come usare la call classe , vedere Procedura: Fornire funzioni di lavoro alle classi di chiamata e trasformatore.

[Torna all'inizio]

Classe transformer

La classe concurrency::transformer funge sia da ricevitore di messaggi che come mittente del messaggio. La transformer classe è simile alla call classe perché esegue una funzione di lavoro definita dall'utente quando riceve i dati. Tuttavia, la transformer classe invia anche il risultato della funzione di lavoro agli oggetti ricevitore. Analogamente a un call oggetto, un transformer oggetto agisce in parallelo ad altri componenti che inviano messaggi. Se un transformer oggetto funziona quando riceve un messaggio, aggiunge tale messaggio a una coda. Ogni transformer oggetto elabora i messaggi in coda nell'ordine in cui vengono ricevuti.

La transformer classe invia il messaggio a una destinazione. Se si imposta il _PTarget parametro nel costruttore su NULL, è possibile specificare successivamente la destinazione chiamando il metodo concurrency::link_target .

A differenza di tutti gli altri tipi di blocchi di messaggi asincroni forniti dalla libreria agenti, la transformer classe può agire su tipi di input e output diversi. Questa possibilità di trasformare i dati da un tipo a un altro rende la transformer classe un componente chiave in molte reti simultanee. Inoltre, è possibile aggiungere funzionalità parallele con granularità fine nella funzione di lavoro di un transformer oggetto .

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la transformer classe . In questo esempio viene creato un transformer oggetto che moltiplica ogni valore di input int di 0,33 per produrre un double valore come output. L'esempio riceve quindi i valori trasformati dallo stesso transformer oggetto e li stampa nella console.

// transformer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create an transformer object that receives int data and 
   // sends double data.
   transformer<int, double> third([](int n) {
      // Return one-third of the input value.
      return n * 0.33;
   });

   // Send a few items to the transformer object.
   send(third, 33);
   send(third, 44);
   send(third, 55);

   // Read the processed items from the transformer object and print
   // them to the console.
   wcout << receive(third) << endl;
   wcout << receive(third) << endl;
   wcout << receive(third) << endl;
}

Nell'esempio viene prodotto l'output seguente:

10.8914.5218.15

Per un esempio completo che illustra come usare la transformer classe , vedere Procedura: Usare il trasformatore in una pipeline di dati.

[Torna all'inizio]

Classe choice

La classe concurrency::choice seleziona il primo messaggio disponibile da un set di origini. La choice classe rappresenta un meccanismo di flusso di controllo anziché un meccanismo di flusso di dati (l'argomento Libreria degli agenti asincroni descrive le differenze tra il flusso di dati e il flusso di controllo).

La lettura da un oggetto choice è simile alla chiamata della funzione WaitForMultipleObjects API Di Windows quando il bWaitAll parametro è impostato su FALSE. Tuttavia, la choice classe associa i dati all'evento stesso anziché a un oggetto di sincronizzazione esterno.

In genere, si usa la choice classe insieme alla funzione concurrency::receive per guidare il flusso di controllo nell'applicazione. Usare la choice classe quando è necessario selezionare tra buffer di messaggi con tipi diversi. Usare la single_assignment classe quando è necessario selezionare tra buffer di messaggi con lo stesso tipo.

L'ordine in cui si collegano le origini a un choice oggetto è importante perché può determinare quale messaggio è selezionato. Si consideri ad esempio il caso in cui si collegano più buffer di messaggi che contengono già un messaggio a un choice oggetto . L'oggetto choice seleziona il messaggio dalla prima origine a cui è collegato. Dopo aver collegato tutte le origini, l'oggetto choice mantiene l'ordine in cui ogni origine riceve un messaggio.

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la choice classe . In questo esempio viene usata la funzione concurrency::make_choice per creare un choice oggetto che seleziona tra tre blocchi di messaggi. L'esempio calcola quindi vari numeri di Fibonacci e archivia ogni risultato in un blocco di messaggi diverso. Nell'esempio viene quindi stampato nella console un messaggio basato sull'operazione che è stata completata per prima.

// choice-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace concurrency;
using namespace std;

// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
   if (n < 2)
      return n;
   return fibonacci(n-1) + fibonacci(n-2);
}

int wmain()
{
   // Although the following thee message blocks are written to one time only, 
   // this example illustrates the fact that the choice class works with 
   // different message block types.

   // Holds the 35th Fibonacci number.
   single_assignment<int> fib35;
   // Holds the 37th Fibonacci number.
   overwrite_buffer<int> fib37;
   // Holds half of the 42nd Fibonacci number.
   unbounded_buffer<double> half_of_fib42;   

   // Create a choice object that selects the first single_assignment 
   // object that receives a value.
   auto select_one = make_choice(&fib35, &fib37, &half_of_fib42);

   // Execute a few lengthy operations in parallel. Each operation sends its 
   // result to one of the single_assignment objects.
   parallel_invoke(
      [&fib35] { send(fib35, fibonacci(35)); },
      [&fib37] { send(fib37, fibonacci(37)); },
      [&half_of_fib42] { send(half_of_fib42, fibonacci(42) * 0.5); }
   );

   // Print a message that is based on the operation that finished first.
   switch (receive(select_one))
   {
   case 0:
      wcout << L"fib35 received its value first. Result = " 
            << receive(fib35) << endl;
      break;
   case 1:
      wcout << L"fib37 received its value first. Result = " 
            << receive(fib37) << endl;
      break;
   case 2:
      wcout << L"half_of_fib42 received its value first. Result = " 
            << receive(half_of_fib42) << endl;
      break;
   default:
      wcout << L"Unexpected." << endl;
      break;
   }
}

Questo esempio produce l'output di esempio seguente:

fib35 received its value first. Result = 9227465

Poiché l'attività che calcola il numero 35di Fibonacci non è garantita per la prima fine, l'output di questo esempio può variare.

In questo esempio viene usato l'algoritmo concurrency::p arallel_invoke per calcolare i numeri di Fibonacci in parallelo. Per altre informazioni su parallel_invoke, vedere Algoritmi paralleli.

Per un esempio completo che illustra come usare la choice classe , vedere Procedura: Selezionare tra le attività completate.

[Torna all'inizio]

join e classi multitype_join

Le classi concurrency::join e concurrency::multitype_join consentono di attendere che ogni membro di un set di origini riceva un messaggio. La join classe agisce sugli oggetti di origine con un tipo di messaggio comune. La multitype_join classe agisce sugli oggetti di origine che possono avere tipi di messaggio diversi.

La lettura da un join oggetto o multitype_join è simile alla chiamata della funzione WaitForMultipleObjects API Di Windows quando il bWaitAll parametro è impostato su TRUE. Tuttavia, proprio come un choice oggetto join e multitype_join gli oggetti usano un meccanismo di evento che associa i dati all'evento stesso anziché a un oggetto di sincronizzazione esterno.

La lettura da un join oggetto produce un oggetto std::vector . La lettura da un multitype_join oggetto produce un oggetto std::tuple . Gli elementi vengono visualizzati in questi oggetti nello stesso ordine dei buffer di origine corrispondenti sono collegati all'oggetto join o multitype_join . Poiché l'ordine in cui si collegano i buffer di origine a un join oggetto o multitype_join è associato all'ordine degli elementi nell'oggetto o tuple risultantevector, è consigliabile non scollegare un buffer di origine esistente da un join. Questa operazione può comportare un comportamento non specificato.

Greedy Versus Non-Greedy Joins

Le join classi e multitype_join supportano il concetto di join greedy e non greedy. Un join greedy accetta un messaggio da ognuna delle origini quando i messaggi diventano disponibili fino a quando non sono disponibili tutti i messaggi. Un join non greedy riceve messaggi in due fasi. Prima di tutto, un join non greedy attende fino a quando non viene offerto un messaggio da ognuna delle relative origini. In secondo luogo, dopo che tutti i messaggi di origine sono disponibili, un join non greedy tenta di riservare ognuno di questi messaggi. Se può riservare ogni messaggio, utilizza tutti i messaggi e li propaga alla destinazione. In caso contrario, rilascia o annulla le prenotazioni dei messaggi e attende di nuovo che ogni origine riceva un messaggio.

I join Greedy offrono prestazioni migliori rispetto ai join non greedy perché accettano immediatamente i messaggi. Tuttavia, in rari casi, i join greedy possono causare deadlock. Usare un join non greedy quando sono presenti più join che contengono uno o più oggetti di origine condivisi.

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la join classe . In questo esempio viene usata la funzione concurrency::make_join per creare un join oggetto che riceve da tre single_assignment oggetti. In questo esempio vengono calcolati vari numeri di Fibonacci, ogni risultato viene archiviato in un oggetto diverso single_assignment e quindi viene stampato nella console ogni risultato contenuto dall'oggetto join . Questo esempio è simile all'esempio per la choice classe , ad eccezione del fatto che la join classe attende che tutti i blocchi di messaggi di origine ricevano un messaggio.

// join-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace concurrency;
using namespace std;

// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
   if (n < 2)
      return n;
   return fibonacci(n-1) + fibonacci(n-2);
}

int wmain()
{
   // Holds the 35th Fibonacci number.
   single_assignment<int> fib35;
   // Holds the 37th Fibonacci number.
   single_assignment<int> fib37;
   // Holds half of the 42nd Fibonacci number.
   single_assignment<double> half_of_fib42;   

   // Create a join object that selects the values from each of the
   // single_assignment objects.
   auto join_all = make_join(&fib35, &fib37, &half_of_fib42);

   // Execute a few lengthy operations in parallel. Each operation sends its 
   // result to one of the single_assignment objects.
   parallel_invoke(
      [&fib35] { send(fib35, fibonacci(35)); },
      [&fib37] { send(fib37, fibonacci(37)); },
      [&half_of_fib42] { send(half_of_fib42, fibonacci(42) * 0.5); }
   );

   auto result = receive(join_all);
   wcout << L"fib35 = " << get<0>(result) << endl;
   wcout << L"fib37 = " << get<1>(result) << endl;
   wcout << L"half_of_fib42 = " << get<2>(result) << endl;
}

Nell'esempio viene prodotto l'output seguente:

fib35 = 9227465fib37 = 24157817half_of_fib42 = 1.33957e+008

In questo esempio viene usato l'algoritmo concurrency::p arallel_invoke per calcolare i numeri di Fibonacci in parallelo. Per altre informazioni su parallel_invoke, vedere Algoritmi paralleli.

Per esempi completi che illustrano come usare la join classe , vedere Procedura: Selezionare tra le attività completate e Procedura dettagliata: Uso di join per impedire deadlock.

[Torna all'inizio]

Classe timer

La classe concurrency::timer funge da origine messaggio. Un timer oggetto invia un messaggio a una destinazione dopo che è trascorso un periodo di tempo specificato. La timer classe è utile quando è necessario ritardare l'invio di un messaggio o inviare un messaggio a intervalli regolari.

La timer classe invia il messaggio a una sola destinazione. Se si imposta il _PTarget parametro nel costruttore su NULL, è possibile specificare successivamente la destinazione chiamando il metodo concurrency::ISource::link_target .

Un timer oggetto può essere ripetuto o non ripetuto. Per creare un timer ripetuto, passare true per il _Repeating parametro quando si chiama il costruttore. In caso contrario, passare false per il _Repeating parametro per creare un timer non ripetuto. Se il timer viene ripetuto, invia lo stesso messaggio alla destinazione dopo ogni intervallo.

La libreria agenti crea timer oggetti nello stato non avviato. Per avviare un oggetto timer, chiamare il metodo concurrency::timer::start . Per arrestare un timer oggetto, eliminare definitivamente l'oggetto o chiamare il metodo concurrency::timer::stop . Per sospendere un timer ripetuto, chiamare il metodo concurrency::timer::p ause .

Esempio

Nell'esempio seguente viene illustrata la struttura di base di come usare la timer classe . Nell'esempio vengono utilizzati oggetti e call per segnalare timer lo stato di avanzamento di un'operazione lunga.

// timer-structure.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

// Computes the nth Fibonacci number.
// This function illustrates a lengthy operation and is therefore
// not optimized for performance.
int fibonacci(int n)
{
   if (n < 2)
      return n;
   return fibonacci(n-1) + fibonacci(n-2);
}

int wmain()
{
   // Create a call object that prints characters that it receives 
   // to the console.
   call<wchar_t> print_character([](wchar_t c) {
      wcout << c;
   });

   // Create a timer object that sends the period (.) character to 
   // the call object every 100 milliseconds.
   timer<wchar_t> progress_timer(100u, L'.', &print_character, true);

   // Start the timer.
   wcout << L"Computing fib(42)";
   progress_timer.start();

   // Compute the 42nd Fibonacci number.
   int fib42 = fibonacci(42);

   // Stop the timer and print the result.
   progress_timer.stop();
   wcout << endl << L"result is " << fib42 << endl;
}

Questo esempio produce l'output di esempio seguente:

Computing fib(42)..................................................result is 267914296

Per un esempio completo che illustra come usare la timer classe , vedere Procedura: Inviare un messaggio a un intervallo regolare.

[Torna all'inizio]

Filtro messaggi

Quando si crea un oggetto blocco di messaggi, è possibile fornire una funzione di filtro che determina se il blocco di messaggi accetta o rifiuta un messaggio. Una funzione di filtro è un modo utile per garantire che un blocco di messaggi riceva solo determinati valori.

Nell'esempio seguente viene illustrato come creare un unbounded_buffer oggetto che usa una funzione di filtro per accettare solo numeri pari. L'oggetto unbounded_buffer rifiuta numeri dispari e pertanto non propaga numeri dispari ai blocchi di destinazione.

// filter-function.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create an unbounded_buffer object that uses a filter
   // function to accept only even numbers.
   unbounded_buffer<int> accept_evens(
      [](int n) {
         return (n%2) == 0;
      });

   // Send a few values to the unbounded_buffer object.
   unsigned int accept_count = 0;
   for (int i = 0; i < 10; ++i)
   {
      // The asend function returns true only if the target
      // accepts the message. This enables us to determine
      // how many elements are stored in the unbounded_buffer
      // object.
      if (asend(accept_evens, i))
      {
         ++accept_count;
      }
   }

   // Print to the console each value that is stored in the 
   // unbounded_buffer object. The unbounded_buffer object should
   // contain only even numbers.
   while (accept_count > 0)
   {
      wcout << receive(accept_evens) << L' ';
      --accept_count;
   }
}

Nell'esempio viene prodotto l'output seguente:

0 2 4 6 8

Una funzione di filtro può essere una funzione lambda, un puntatore a funzione o un oggetto funzione. Ogni funzione di filtro accetta una delle forme seguenti.

bool (T)
bool (T const &)

Per eliminare la copia non necessaria dei dati, utilizzare la seconda maschera quando si dispone di un tipo di aggregazione propagato per valore.

Il filtro dei messaggi supporta il modello di programmazione del flusso di dati, in cui i componenti eseguono calcoli quando ricevono dati. Per esempi che usano funzioni di filtro per controllare il flusso di dati in una rete di passaggio di messaggi, vedere Procedura: Usare un filtro di blocco messaggi, Procedura dettagliata: Creazione di un agente di flussi di dati e Procedura dettagliata: Creazione di una rete di elaborazione immagini.

[Torna all'inizio]

Prenotazione messaggi

La prenotazione dei messaggi consente a un blocco di messaggi di riservare un messaggio per un uso successivo. In genere, la prenotazione dei messaggi non viene usata direttamente. Tuttavia, la comprensione della prenotazione dei messaggi consente di comprendere meglio il comportamento di alcuni dei tipi di blocchi di messaggi predefiniti.

Si considerino join non greedy e greedy. Entrambe queste usano la prenotazione dei messaggi per riservare i messaggi per usarli in un secondo momento. Descritto in precedenza, un join non greedy riceve messaggi in due fasi. Durante la prima fase, un oggetto non greedy join attende che ognuna delle relative origini riceva un messaggio. Un join non greedy tenta quindi di riservare ognuno di questi messaggi. Se può riservare ogni messaggio, utilizza tutti i messaggi e li propaga alla destinazione. In caso contrario, rilascia o annulla le prenotazioni dei messaggi e attende di nuovo che ogni origine riceva un messaggio.

Un join greedy, che legge anche i messaggi di input da una serie di origini, usa la prenotazione dei messaggi per leggere messaggi aggiuntivi mentre attende di ricevere un messaggio da ogni origine. Si consideri, ad esempio, un join greedy che riceve messaggi dai blocchi A di messaggi e B. Se il join greedy riceve due messaggi da B ma non ha ancora ricevuto un messaggio da A, il join greedy salva l'identificatore univoco del messaggio per il secondo messaggio da B. Dopo che il join greedy riceve un messaggio da A e propaga questi messaggi, usa l'identificatore del messaggio salvato per verificare se il secondo messaggio da B è ancora disponibile.

È possibile usare la prenotazione dei messaggi quando si implementano tipi di blocchi di messaggi personalizzati. Per un esempio su come creare un tipo di blocco di messaggi personalizzato, vedere Procedura dettagliata: Creazione di un blocco di messaggi personalizzato.

[Torna all'inizio]

Vedi anche

Libreria di agenti asincroni