Blocos de mensagens assíncronos
A Biblioteca de Agentes fornece vários tipos de bloco de mensagens que permitem propagar mensagens entre componentes do aplicativo de maneira thread-safe. Esses tipos de bloco de mensagem geralmente são usados com as várias rotinas de transmissão de mensagens, como concurrency::send, concurrency::asend, concurrency::receive e concurrency::try_receive. Para obter mais informações sobre as rotinas de transmissão de mensagens definidas pela Biblioteca de Agentes, consulte Funções de Transmissão de Mensagens.
Seções
Este tópico contém as seguintes seções:
Origens e Destinos
Fontes e destinos são dois participantes importantes na transmissão de mensagens. Uma origem se refere a um ponto de extremidade de comunicação que envia mensagens. Um destino se refere a um ponto de extremidade de comunicação que recebe mensagens. Você pode pensar em uma origem como um ponto de extremidade do qual você lê e um destino como um ponto de extremidade no qual você grava. Os aplicativos conectam origens e destinos para formar redes de mensagens.
A Biblioteca de Agentes usa duas classes abstratas para representar origens e destinos: concurrency::ISource e concurrency::ITarget. Tipos de blocos de mensagem que atuam como fontes derivam de ISource
; tipos de blocos de mensagem que atuam como destinos derivam de ITarget
. Os tipos de bloco de mensagens que atuam como origens e destinos derivam de ISource
e ITarget
.
Propagação de mensagens
A propagação de mensagens é o ato de enviar uma mensagem de um componente para outro. Quando uma mensagem é oferecida a um bloco de mensagens, essa mensagem pode ser aceita, recusada ou adiada. Cada tipo de bloco de mensagem armazena e transmite mensagens de diferentes maneiras. Por exemplo, a classe unbounded_buffer
armazena um número ilimitado de mensagens, a classe overwrite_buffer
armazena uma única mensagem por vez e a classe transformadora armazena uma versão alterada de cada mensagem. Esses tipos de bloco de mensagens estão descritos mais detalhadamente mais adiante neste documento.
Quando um bloco de mensagens aceita uma mensagem, ele pode, opcionalmente, executar o trabalho e, se o bloco de mensagem for uma origem, passar a mensagem resultante para outro membro da rede. Um bloco de mensagens pode usar uma função de filtro para recusar mensagens que não deseja receber. Os filtros estão descritos mais detalhadamente mais adiante neste tópico, na seção Filtragem de Mensagem. Um bloco de mensagens que adia uma mensagem pode reservar essa mensagem e consumi-la mais tarde. A reserva de mensagens está descrita em mais detalhes posteriormente neste tópico, na seção Reserva de Mensagem.
A Biblioteca de Agentes permite que blocos de mensagens passem mensagens de forma assíncrona ou síncrona. Quando você passa uma mensagem para um bloco de mensagem de forma síncrona, por exemplo, usando a função send
, o runtime bloqueia o contexto atual até que o bloco de destino aceite ou rejeite a mensagem. Quando você passa uma mensagem para um bloco de mensagem de forma assíncrona, por exemplo, usando a função asend
, o runtime oferece a mensagem para o destino e, se o destino aceitar a mensagem, o runtime agenda uma tarefa assíncrona que propaga a mensagem para o receptor. O runtime usa tarefas leves para propagar mensagens de maneira cooperativa. Para obter mais informações sobre tarefas leves, confira Agendador de Tarefas.
Os aplicativos conectam origens e destinos para formar redes de mensagens. Normalmente, você vincula a rede e chama send
ou asend
para passar os dados para a rede. Para conectar um bloco de mensagem de origem a um destino, chame o método concurrency::ISource::link_target. Para desconectar um bloco de origem de um destino, chame o método concurrency::ISource::unlink_target. Para desconectar um bloco de origem de todos os seus destinos, chame o método concurrency::ISource::unlink_targets. Quando um dos tipos de bloco de mensagem predefinidos deixa o escopo ou é destruído, ele se desconecta automaticamente de qualquer bloco de destino. Alguns tipos de bloco de mensagens restringem o número máximo de destinos nos quais podem gravar. A seção a seguir descreve as restrições que se aplicam aos tipos de bloco de mensagens predefinidos.
Visão geral dos tipos de bloco de mensagens
A tabela a seguir descreve brevemente a função dos tipos importantes de bloco de mensagens.
unbounded_buffer
Armazena uma fila de mensagens.
overwrite_buffer
Armazena uma mensagem que pode ser gravada e lida várias vezes.
single_assignment
Armazena uma mensagem que pode ser gravada uma vez e lida várias vezes.
call
Executa o trabalho quando recebe uma mensagem.
transformador
Executa o trabalho quando recebe dados e envia o resultado desse trabalho para outro bloco de destino. A classe transformer
pode agir em diferentes tipos de entrada e saída.
choice
Seleciona a primeira mensagem disponível de um conjunto de origens.
join e multitype join
Aguarda até que todas as mensagens sejam recebidas de um conjunto de origens e, em seguida, combina as mensagens em uma mensagem para outro bloco de mensagens.
timer
Envia uma mensagem para um bloco de destino em um intervalo regular.
Esses tipos de bloco de mensagens têm características diferentes que os tornam úteis para situações diferentes. Estas são algumas das características:
Tipo de propagação: se o bloco de mensagens atua como uma fonte de dados, um receptor de dados ou ambos.
Ordenação de mensagem: se o bloco de mensagens mantém a ordem original na qual as mensagens são enviadas ou recebidas. Cada tipo de bloco de mensagem predefinido mantém a ordem original na qual envia ou recebe mensagens.
Contagem de fonte: o número máximo de fontes das quais o bloco de mensagens pode ser lido.
Contagem de destino: o número máximo de destinos nos quais o bloco de mensagem pode gravar.
A tabela a seguir mostra como essas características se relacionam com os vários tipos de bloco de mensagens.
Tipo de bloco de mensagens | Tipo de propagação (origem, destino ou ambos) | Ordenação de mensagens (ordenada ou não ordenada) | Contagem de origem | Contagem de destino |
---|---|---|---|---|
unbounded_buffer |
Ambos | Encomenda feita | Não associado | Não associado |
overwrite_buffer |
Ambos | Encomenda feita | Não associado | Não associado |
single_assignment |
Ambos | Encomenda feita | Não associado | Não associado |
call |
Destino | Encomenda feita | Não associado | Não Aplicável |
transformer |
Ambos | Encomenda feita | Não associado | 1 |
choice |
Ambos | Encomenda feita | 10 | 1 |
join |
Ambos | Encomenda feita | Não associado | 1 |
multitype_join |
Ambos | Encomenda feita | 10 | 1 |
timer |
Fonte | Não Aplicável | Não Aplicável | 1 |
As seções a seguir descrevem esses tipos de blocos de mensagens de forma detalhada.
classe unbounded_buffer
A classe concurrency::unbounded_buffer representa uma estrutura de sistema de mensagens assíncrona para fins gerais. Essa classe armazena uma fila PEPS (primeiro a entrar, primeiro a sair) de mensagens que podem ser gravadas por várias origens ou lidas por vários destinos. Quando um destino recebe uma mensagem de um objeto unbounded_buffer
, essa mensagem é removida da fila de mensagens. Portanto, embora um objeto unbounded_buffer
possa ter vários destinos, cada mensagem será recebida por apenas um destino. A classe unbounded_buffer
é útil quando você deseja passar várias mensagens para outro componente e esse componente deve receber cada uma das mensagens.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe unbounded_buffer
. Este exemplo envia três valores para um objeto unbounded_buffer
e lê esses valores de volta do mesmo objeto.
// 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;
}
Esse exemplo gera a saída a seguir:
334455
Para obter um exemplo completo que mostra como usar a classe unbounded_buffer
, consulte Como implementar vários padrões de produtor-consumidor.
Classe overwrite_buffer
A classe concurrency::overwrite_buffer se assemelha à classe unbounded_buffer
, exceto que um objeto overwrite_buffer
armazena apenas uma mensagem. Além disso, quando um destino recebe uma mensagem de um objeto overwrite_buffer
, essa mensagem não é removida do buffer. Portanto, vários destinos recebem uma cópia da mensagem.
A classe overwrite_buffer
é útil quando você desejar passar várias mensagens para outro componente mas esse componente precisa apenas do valor mais recente. Essa classe também é útil quando você deseja difundir uma mensagem para vários componentes.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe overwrite_buffer
. Este exemplo envia três valores para um objeto overwrite _buffer
e lê o valor atual do mesmo objeto três vezes. Este exemplo é semelhante ao exemplo da classe unbounded_buffer
. No entanto, a classe overwrite_buffer
armazena apenas uma mensagem. Além disso, o runtime não remove a mensagem de um objeto overwrite_buffer
após a leitura.
// 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;
}
Esse exemplo gera a saída a seguir:
555555
Para obter um exemplo completo que mostra como usar a classe overwrite_buffer
, consulte Como implementar vários padrões de produtor-consumidor.
Classe single_assignment
A classe concurrency::single_assignment é semelhante à classe overwrite_buffer
, exceto no ponto em que um objeto single_assignment
pode ser gravado apenas uma vez. Como na classe overwrite_buffer
, quando um destino recebe uma mensagem de um objeto single_assignment
, essa mensagem não é removida do objeto. Portanto, vários destinos recebem uma cópia da mensagem. A classe single_assignment
é útil quando você deseja difundir uma mensagem para vários componentes.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe single_assignment
. Este exemplo envia três valores para um objeto single_assignment
e lê o valor atual do mesmo objeto três vezes. Este exemplo é semelhante ao exemplo da classe overwrite_buffer
. Embora as classes overwrite_buffer
e single_assignment
armazenem uma única mensagem, a classe single_assignment
pode ser gravada apenas uma vez.
// 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;
}
Esse exemplo gera a saída a seguir:
333333
Para obter um exemplo completo que mostra como usar a classe single_assignment
, consulte Instruções passo a passo: implementando futuros.
Classe da chamada
A classe concurrency::call atua como um receptor de mensagem que executa uma função de trabalho quando recebe dados. Essa função de trabalho pode ser uma expressão lambda, um objeto de função ou um ponteiro de função. Um objeto call
se comporta de forma diferente de uma chamada de função comum porque atua em paralelo com outros componentes que enviam mensagens para ele. Se um objeto call
estiver executando o trabalho quando receber uma mensagem, ele adicionará essa mensagem a uma fila. Cada objeto call
processa mensagens enfileiradas na ordem em que são recebidas.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe call
. Este exemplo cria um objeto call
que imprime cada valor recebido no console. Em seguida, o exemplo envia três valores para o objeto call
. Como o objeto call
processa mensagens em um thread separado, este exemplo também usa uma variável de contador e um objeto de evento para garantir que o objeto call
processe todas as mensagens antes que a função wmain
retorne.
// 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();
}
Esse exemplo gera a saída a seguir:
334455
Para obter um exemplo completo que mostra como usar a classe call
, consulte Como fornecer funções de trabalho para as classes call e transformer.
Classe transformer
A classe concurrency::transformer atua como um receptor de mensagem e como um remetente de mensagem. A classe transformer
se assemelha à classe call
porque executa uma função de trabalho definida pelo usuário quando recebe dados. No entanto, a classe transformer
também envia o resultado da função de trabalho para objetos receptores. Como um objeto call
, um objeto transformer
atua em paralelo com outros componentes que enviam mensagens para ele. Se um objeto transformer
estiver executando o trabalho quando receber uma mensagem, ele adicionará essa mensagem a uma fila. Cada objeto transformer
processa suas mensagens enfileiradas na ordem em que são recebidas.
A classe transformer
envia sua mensagem para um destino. Se você definir o parâmetro _PTarget
no construtor como NULL
, poderá especificar posteriormente o destino chamando o método concurrency::link_target.
Ao contrário de todos os outros tipos de bloco de mensagens assíncronos fornecidos pela Biblioteca de Agentes, a classe transformer
pode agir em diferentes tipos de entrada e saída. Essa capacidade de transformar dados de um tipo em outro torna a classe transformer
um componente chave em muitas redes simultâneas. Além disso, você pode adicionar mais funcionalidade paralela refinada na função de trabalho de um objeto transformer
.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe transformer
. Este exemplo cria um objeto transformer
que multiplica cada valor de entrada int
por 0,33 para produzir um valor double
como saída. Em seguida, o exemplo recebe os valores transformados do mesmo objeto transformer
e os imprime no 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;
}
Esse exemplo gera a saída a seguir:
10.8914.5218.15
Para obter um exemplo completo que mostra como usar a classe transformer
, consulte Como usar transformador em um pipeline de dados.
Classe choice
A classe concurrency::choice seleciona a primeira mensagem disponível em um conjunto de fontes. A classe choice
representa um mecanismo de fluxo de controle em vez de um mecanismo de fluxo de dados (o tópico Biblioteca de Agentes Assíncronos descreve as diferenças entre fluxo de dados e fluxo de controle).
A leitura de um objeto de escolha se assemelha à chamada da função de API do Windows WaitForMultipleObjects
quando ela tem o parâmetro bWaitAll
definido como FALSE
. No entanto, a classe choice
associa dados ao evento em si, em vez de a um objeto de sincronização externo.
Normalmente, você usa a classe choice
junto com a função concurrency::receive para direcionar o fluxo de controle em seu aplicativo. Use a classe choice
quando precisar selecionar entre buffers de mensagem que tenham tipos diferentes. Use a classe single_assignment
quando precisar selecionar entre buffers de mensagem que tenham os mesmos tipos.
A ordem na qual você vincula as fontes a um objeto choice
é importante porque pode determinar qual mensagem está selecionada. Por exemplo, considere o caso em que você vincula vários buffers de mensagem que já contêm uma mensagem a um objeto choice
. O objeto choice
seleciona a mensagem da primeira fonte à qual ele está vinculado. Depois de vincular todas as fontes, o objeto choice
preservará a ordem na qual cada fonte recebe uma mensagem.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe choice
. Este exemplo usa a função concurrency::make_choice para criar um objeto choice
que seleciona entre três blocos de mensagem. Em seguida, o exemplo calcula vários números de Fibonacci e armazena cada resultado em um bloco de mensagens diferente. Em seguida, o exemplo imprime no console uma mensagem baseada na operação que terminou primeiro.
// 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;
}
}
Este exemplo gera a seguinte saída de amostra:
fib35 received its value first. Result = 9227465
Como a tarefa que calcula o 35o número de Fibonacci não tem garantia de ser concluída primeiro, a saída deste exemplo pode variar.
Este exemplo usa o algoritmo concurrency::parallel_invoke para calcular os números de Fibonacci em paralelo. Para obter mais informações sobre parallel_invoke
, consulte Algoritmos paralelos.
Para obter um exemplo completo que mostra como usar a classe choice
, consulte Como selecionar tarefas concluídas.
participar e multitype_join Classes
As classes concurrency::join e concurrency::multitype_join permitem que você aguarde cada membro de um conjunto de fontes receber uma mensagem. A classe join
atua em objetos de origem que têm um tipo de mensagem comum. A classe multitype_join
atua em objetos de origem que podem ter tipos de mensagem diferentes.
A leitura de um objeto join
ou multitype_join
se assemelha à chamada da função de API do Windows WaitForMultipleObjects
quando ela tem o parâmetro bWaitAll
definido como TRUE
. No entanto, assim como um objeto choice
, os objetos join
e multitype_join
usam um mecanismo de evento que associa dados ao evento em si, e não a um objeto de sincronização externo.
A leitura de um objeto join
produz um objeto std::vector. A leitura de um objeto multitype_join
produz um objeto std::tuple. Os elementos aparecem nesses objetos na mesma ordem em que seus buffers de origem correspondentes estão vinculados ao objeto join
ou multitype_join
. Como a ordem na qual você vincula os buffers de origem a um objeto join
ou multitype_join
está associada à ordem dos elementos no objeto vector
ou tuple
resultante, recomendamos que você não desvincule um buffer de origem existente de uma junção. Fazer isso pode resultar em um comportamento não especificado.
Junções Greedy Contra Não Greedy
As classes join
e multitype_join
dão suporte ao conceito de junções greedy e não greedy. Uma junção greedy aceita uma mensagem de cada uma de suas fontes à medida que as mensagens ficam disponíveis até que todas as mensagens estejam disponíveis. Uma junção não greedy recebe mensagens em duas fases. Primeiro, uma junção não greedy aguarda até receber uma mensagem de cada uma de suas fontes. Em segundo lugar, depois que todas as mensagens de origem estão disponíveis, uma junção não greedy tenta reservar cada uma dessas mensagens. Se puder reservar cada mensagem, ela consumirá todas as mensagens e as propagará para seu destino. Caso contrário, ele libera ou cancela as reservas de mensagens e aguarda novamente que cada fonte receba uma mensagem.
As junções greedy têm um desempenho melhor do que as junções não greedy, porque aceitam mensagens imediatamente. No entanto, em casos raros, junções greedy podem resultar em deadlocks. Use uma junção não greedy quando você tiver várias junções que contêm um ou mais objetos de origem compartilhados.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe join
. Este exemplo usa a função concurrency::make_join para criar um objeto join
que recebe de três objetos single_assignment
. Este exemplo calcula vários números de Fibonacci, armazena cada resultado em um objeto single_assignment
diferente e, em seguida, imprime no console cada resultado que o objeto join
contém. Este exemplo é semelhante ao exemplo da classe choice
, exceto que a classe join
aguarda todos os blocos de mensagem de origem receberem uma mensagem.
// 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;
}
Esse exemplo gera a saída a seguir:
fib35 = 9227465fib37 = 24157817half_of_fib42 = 1.33957e+008
Este exemplo usa o algoritmo concurrency::parallel_invoke para calcular os números de Fibonacci em paralelo. Para obter mais informações sobre parallel_invoke
, consulte Algoritmos paralelos.
Para obter exemplos completos que mostram como usar a classe join
, consulte Como selecionar tarefas concluídas e Instruções passo a passo: usando join para Evitar Deadlock.
Classe timer
concurrency::timer class atua como a origem da mensagem. Um objeto timer
envia uma mensagem para um destino depois que um período de tempo especificado tiver decorrido. A classe timer
é útil quando você deve atrasar o envio de uma mensagem ou deseja enviar uma mensagem em um intervalo regular.
A classe timer
envia sua mensagem para apenas um destino. Se você definir o parâmetro _PTarget
no construtor como NULL
, poderá especificar posteriormente o destino chamando o método concurrency::ISource::link_target.
Um objeto timer
pode ser repetitivo ou não repetitivo. Para criar um temporizador repetido, passe true
para o parâmetro _Repeating
quando você chamar o construtor. Caso contrário, passe false
para o parâmetro _Repeating
para criar um temporizador não repetitivo. Se o temporizador estiver repetindo, ele enviará a mesma mensagem para seu destino após cada intervalo.
A Biblioteca de Agentes cria objetos timer
no estado não iniciado. Para iniciar um objeto de temporizador, chame o método concurrency::timer::start. Para parar um objeto timer
, destrua o objeto ou chame o método concurrency::timer::stop. Para pausar um temporizador repetido, chame o método concurrency::timer::pause.
Exemplo
O exemplo a seguir mostra a estrutura básica de como trabalhar com a classe timer
. O exemplo usa objetos timer
e call
para relatar o progresso de uma operação demorada.
// 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;
}
Este exemplo gera a seguinte saída de amostra:
Computing fib(42)..................................................result is 267914296
Para obter um exemplo completo que mostra como usar a classe timer
, consulte Como enviar uma mensagem em um intervalo regular.
Filtragem de mensagens
Quando você cria um objeto de bloco de mensagem, você pode fornecer uma função de filtro que determina se o bloco de mensagem aceita ou rejeita uma mensagem. Uma função de filtro é um modo útil de garantir que um bloco de mensagens receba apenas determinados valores.
O exemplo a seguir mostra como criar um objeto unbounded_buffer
que usa uma função de filtro para aceitar apenas números par. O objeto unbounded_buffer
rejeita números ímpares e, portanto, não propaga números ímpares para seus blocos de destino.
// 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;
}
}
Esse exemplo gera a saída a seguir:
0 2 4 6 8
Uma função de filtro pode ser uma função lambda, um ponteiro de função ou um objeto de função. Cada função de filtro usa uma das seguintes formas.
bool (T)
bool (T const &)
Para eliminar a cópia desnecessária de dados, use a segunda forma quando você tiver um tipo de agregação propagado por valor.
A filtragem de mensagens dá suporte ao modelo de programação de fluxo de dados, no qual os componentes executam cálculos quando recebem dados. Para obter exemplos que usam funções de filtro para controlar o fluxo de dados em uma rede de transmissão de mensagens, consulte Como usar um filtro de bloco de mensagens, Instruções passo a passo: criando um agente de fluxo de dados e Instruções passo a passo: criando uma rede de processamento de imagem.
Mensagem de Reserva
A reserva de mensagens permite que um bloco de mensagens reserve uma mensagem para uso posterior. Normalmente, a reserva de mensagens não é usada diretamente. No entanto, entender a reserva de mensagens pode ajudá-lo a entender melhor o comportamento de alguns tipos de bloco de mensagens predefinidos.
Considere junções não greedy e greedy. Ambos usam a reserva de mensagens para reservar mensagens para uso posterior. Um descrito anteriormente, uma junção não greedy recebe mensagens em duas fases. Durante a primeira fase, um objeto join
não greedy aguarda que cada uma de suas fontes receba uma mensagem. Uma junção não greedy tenta reservar cada uma dessas mensagens. Se puder reservar cada mensagem, ela consumirá todas as mensagens e as propagará para seu destino. Caso contrário, ele libera ou cancela as reservas de mensagens e aguarda novamente que cada fonte receba uma mensagem.
Uma junção greedy, que também lê mensagens de entrada de várias fontes, usa a reserva de mensagens para ler mensagens adicionais enquanto aguarda para receber uma mensagem de cada fonte. Por exemplo, considere uma junção greedy que recebe mensagens de blocos de mensagens A
e B
. Se a junção greedy receber duas mensagens de B, mas ainda não tiver recebido uma mensagem de A
, a junção greedy salvará o identificador de mensagem exclusivo da segunda mensagem de B
. Depois que a junção greedy recebe uma mensagem de A
e propaga essas mensagens, ela usa o identificador de mensagem salvo para ver se a segunda mensagem de B
ainda está disponível.
Você pode usar a reserva de mensagens ao implementar seus próprios tipos de bloco de mensagens personalizados. Para obter um exemplo sobre como criar um tipo de bloco de mensagem personalizado, consulte Instruções passo a passo: criando um bloco de mensagens personalizado.