Bloques de mensajes asincrónicos
La Biblioteca de agentes proporciona varios tipos de bloques de mensajes que permiten propagar mensajes entre los componentes de aplicación de una manera segura para subprocesos. Estos tipos de bloques de mensajes se suelen usar con las distintas rutinas de paso de mensajes, como concurrency::send, concurrency::asend, concurrency::receive y concurrency::try_receive. Para obtener más información sobre las rutinas de paso de mensajes que se definen en la Biblioteca de agentes, vea Funciones que pasan mensajes.
Secciones
Este tema contiene las siguientes secciones:
Orígenes y destinos
Propagación de mensajes
Información general sobre los tipos de bloques de mensajes
Clase unbounded_buffer
Clase overwrite_buffer
Clase single_assignment
Clase call
Clase transformer
Clase choice
Clases join y multitype_join
Clase timer
Filtrado de mensajes
Reserva de mensajes
Orígenes y destinos
Los orígenes y los destinos son dos participantes importantes en el paso de mensajes. El origen se refiere a un extremo de comunicación que envía mensajes. El destino se refiere a un extremo de comunicación que recibe mensajes. Puede considerar el origen como un extremo del que se lee y el destino como un extremo en el que se escribe. Las aplicaciones conectan los orígenes y los destinos entre sí para formar redes de mensajería.
La Biblioteca de agentes usa dos clases abstractas para representar orígenes y destinos: concurrency::ISource y concurrency::ITarget. Los tipos de bloques de mensajes que actúan como orígenes derivan de ISource; los tipos de bloques de mensajes que actúan como destinos derivan de ITarget. Los tipos de bloques de mensajes que actúan como orígenes y destinos derivan de ISource y de ITarget.
[Arriba]
Propagación de mensajes
La propagación de mensajes es el acto de enviar un mensaje de un componente a otro. Cuando se ofrece un mensaje a un bloque de mensajes, este puede aceptar, rechazar o posponer ese mensaje. Cada tipo de bloque de mensajes almacena y transmite mensajes de maneras diferentes. Por ejemplo, la clase unbounded_buffer almacena un número ilimitado de mensajes, la clase overwrite_buffer almacena un único mensaje cada vez y la clase transformer almacena una versión modificada de cada mensaje. Estos tipos de bloques de mensajes se describen con más detalle más adelante en este documento.
Cuando un bloque de mensajes acepta un mensaje, puede realizar opcionalmente trabajo y, si el bloque de mensajes es un origen, pasar el mensaje resultante a otro miembro de la red. Un bloque de mensajes puede usar una función de filtro para rechazar los mensajes que no desea recibir. Los filtros se describen con más detalle más adelante en este tema, en la sección Filtrado de mensajes. Un bloque de mensajes que pospone un mensaje puede reservar ese mensaje y usarlo posteriormente. La reserva de mensajes se describe con más detalle más adelante en este tema, en la sección Reserva de mensajes.
La Biblioteca de agentes permite a los bloques de mensajes pasar mensajes de forma sincrónica o asincrónica. Cuando se pasa un mensaje a un bloque de mensajes sincrónicamente, por ejemplo mediante la función send, el runtime bloquea el contexto actual hasta que el bloque de destino acepta o rechaza el mensaje. Cuando se pasa un mensaje a un bloque de mensajes asincrónicamente, por ejemplo mediante la función asend, el runtime ofrece el mensaje al destino, y si el destino acepta el mensaje, el runtime programa una tarea asincrónica que propaga el mensaje al receptor. El runtime usa tareas ligeras para propagar mensajes de manera cooperativa. Para obtener más información sobre las tareas ligeras, vea Programador de tareas (Runtime de simultaneidad).
Las aplicaciones conectan los orígenes y los destinos entre sí para formar redes de mensajería. Normalmente, se enlaza la red y se llama a send o asend para pasar datos a la red. Para conectar un bloque de mensajes de origen a un destino, llame al método concurrency::ISource::link_target. Para desconectar un bloque de origen de un destino, llame al método concurrency::ISource::unlink_target. Para desconectar un bloque de origen de todos sus destinos, llame al método concurrency::ISource::unlink_targets. Cuando uno de los tipos de bloques de mensajes predefinido abandona el ámbito o se destruye, se desconecta automáticamente a sí mismo de cualquier bloque de destino. Algunos tipos de bloques de mensajes limitan el número máximo de destinos en los que pueden escribir. En la próxima sección se describen las restricciones que se aplican a los tipos predefinidos de bloques de mensajes.
[Arriba]
Información general sobre los tipos de bloques de mensajes
En la siguiente tabla se describe brevemente el rol de los tipos de bloques de mensajes.
unbounded_buffer
Almacena una cola de mensajes.overwrite_buffer
Almacena un mensaje en el que se puede escribir y del que se puede leer varias veces.single_assignment
Almacena un mensaje en el que se puede escribir una vez y del que se puede leer varias veces.call
Realiza trabajo cuando recibe un mensaje.transformer
Realiza trabajo cuando recibe datos y envía el resultado de ese trabajo a otro bloque de destino. La clase transformer puede actuar en diferentes tipos de entrada y salida.choice
Selecciona el primer mensaje disponible en un conjunto de orígenes.join y multitype join
Esperan por todos los mensajes que se van a recibir de un conjunto de orígenes y, a continuación, combinan los mensajes en un mensaje para otro bloque de mensajes.timer
Envía un mensaje a un bloque de destino de manera periódica.
Estos tipos de bloques de mensajes tienen diferentes características que los hacen útiles en distintas situaciones. A continuación se indican algunas de estas características:
Tipo de propagación: indica si el bloque de mensajes actúa como origen de datos, receptor de datos o ambos.
Orden de mensajes: indica si el bloque de mensajes mantiene el orden original en que se envían o reciben los mensajes. Cada tipo de bloque de mensajes predefinido mantiene el orden original en el que envía o recibe los mensajes.
Recuento de orígenes: el número máximo de orígenes donde el bloque de mensajes puede leer.
Recuento de destinos: el número máximo de destinos donde el bloque de mensajes puede escribir.
En la siguiente tabla se muestra cómo se relacionan estas características con los diferentes tipos de bloques de mensajes.
Tipo de bloque de mensajes |
Tipo de propagación (origen, destino, ambos) |
Orden de mensajes (ordenado o no ordenado) |
Recuento de orígenes |
Recuento de destinos |
---|---|---|---|---|
unbounded_buffer |
Ambos |
Por orden |
Ilimitado |
Ilimitado |
overwrite_buffer |
Ambos |
Por orden |
Ilimitado |
Ilimitado |
single_assignment |
Ambos |
Por orden |
Ilimitado |
Ilimitado |
call |
Destino |
Por orden |
Ilimitado |
No es aplicable |
transformer |
Ambos |
Por orden |
Ilimitado |
1 |
choice |
Ambos |
Por orden |
10 |
1 |
join |
Ambos |
Por orden |
Ilimitado |
1 |
multitype_join |
Ambos |
Por orden |
10 |
1 |
timer |
Origen |
No es aplicable |
No es aplicable |
1 |
En las secciones siguientes se describen los tipos de bloques de mensajes con más detalle.
[Arriba]
Clase unbounded_buffer
La clase concurrency::unbounded_buffer representa una estructura de mensajería asincrónica de uso general. Esta clase almacena una cola FIFO (primero en entrar, primero en salir) de mensajes donde varios orígenes pueden escribir o de los que varios destinos pueden leer. Cuando un destino recibe un mensaje de un objeto unbounded_buffer, ese mensaje se quita de la cola de mensajes. Por tanto, aunque un objeto unbounded_buffer puede tener varios destinos, solo uno recibirá cada mensaje. La clase unbounded_buffer resulta útil si desea pasar varios mensajes a otro componente, y ese componente debe recibir cada mensaje.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase unbounded_buffer. En este ejemplo se envían tres valores a un objeto unbounded_buffer y, a continuación, se vuelven a leer esos valores del mismo 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;
}
Este ejemplo produce el siguiente resultado:
Para obtener un ejemplo completo que muestra cómo se usa la clase unbounded_buffer, vea Cómo: Implementar varios modelos productor-consumidor.
[Arriba]
Clase overwrite_buffer
La clase concurrency::overwrite_buffer es similar a la clase unbounded_buffer, excepto por el hecho de que un objeto overwrite_buffer solo almacena un mensaje. Además, cuando un destino recibe un mensaje de un objeto overwrite_buffer, ese mensaje no se quita del búfer. Por tanto, varios destinos reciben una copia del mensaje.
La clase overwrite_buffer resulta útil si desea pasar varios mensajes a otro componente, pero este componente solo necesita el valor más reciente. Esta clase también resulta útil si desea difundir un mensaje a varios componentes.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase overwrite_buffer. En este ejemplo se envían tres valores a un objeto overwrite _buffer y, a continuación, se lee valor actual del mismo objeto tres veces. Este ejemplo es similar al ejemplo de la clase unbounded_buffer. Sin embargo, la clase overwrite_buffer solo almacena un mensaje. Además, el runtime no quita el mensaje de un objeto overwrite_buffer después de leerlo.
// 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;
}
Este ejemplo produce el siguiente resultado:
Para obtener un ejemplo completo que muestra cómo se usa la clase overwrite_buffer, vea Cómo: Implementar varios modelos productor-consumidor.
[Arriba]
Clase single_assignment
La clase concurrency::single_assignment es similar a la clase overwrite_buffer, excepto por el hecho de que solo se puede escribir una vez en un objeto single_assignment. Al igual que la clase overwrite_buffer, cuando un destino recibe un mensaje de un objeto single_assignment, el mensaje no se quita de dicho objeto. Por tanto, varios destinos reciben una copia del mensaje. La clase single_assignment resulta útil si desea difundir un mensaje a varios componentes.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase single_assignment. En este ejemplo se envían tres valores a un objeto single_assignment y, a continuación, se lee valor actual del mismo objeto tres veces. Este ejemplo es similar al ejemplo de la clase overwrite_buffer. Aunque las clases overwrite_buffer y single_assignment almacenan un único mensaje, solo se puede escribir una vez en la clase single_assignment.
// 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;
}
Este ejemplo produce el siguiente resultado:
Para obtener un ejemplo completo que muestra cómo se usa la clase single_assignment, vea Tutorial: Implementar futuros.
[Arriba]
Clase call
La clase concurrency::call actúa como un receptor de mensajes que realiza una función de trabajo cuando recibe datos. Esta función de trabajo puede ser una expresión lambda, un objeto de función o un puntero a una función. Un objeto call se comporta de manera diferente que una llamada de función ordinaria, ya que actúa en paralelo con otros componentes que le envían mensajes. Si un objeto call está realizando trabajo cuando recibe un mensaje, agrega ese mensaje a una cola. Cada objeto call procesa los mensajes en cola en el orden en el que se recibieron.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase call. En este ejemplo se crea un objeto call que imprime en la consola cada valor que recibe. A continuación se envían tres valores al objeto call. Puesto que el objeto call procesa los mensajes en un subproceso independiente, en este ejemplo también se usa una variable de contador y un objeto event para asegurarse de que el objeto call procesa todos los mensajes antes de que vuelva la función wmain.
// 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();
}
Este ejemplo produce el siguiente resultado:
Para obtener un ejemplo completo que muestra cómo se usa la clase call, vea Cómo: Proporcionar funciones de trabajo a las clases call y transformer.
[Arriba]
Clase transformer
La clase concurrency::transformer actúa a la vez como receptor y como remitente de mensajes. La clase transformer es similar a la clase call, ya que realiza una función de trabajo definida por el usuario cuando recibe datos. Sin embargo, la clase transformer también envía el resultado de la función de trabajo a objetos receptores. Al igual que un objeto call, el objeto transformer actúa en paralelo con otros componentes que le envían mensajes. Si un objeto transformer está realizando trabajo cuando recibe un mensaje, agrega ese mensaje a una cola. Cada objeto transformer procesa sus mensajes en cola en el orden en el que se recibieron.
La clase transformer envía su mensaje a un único destino. Si establece el parámetro _PTarget del constructor en NULL, puede especificar el destino más adelante mediante un llamada al método concurrency::link_target.
A diferencia de los demás tipos de bloques de mensajes asincrónicos que proporciona la Biblioteca de agentes, la clase transformer puede actuar en tipos de entrada y salida diferentes. Esta capacidad de transformar los datos de un tipo a otro convierte a la clase transformer en un componente clave en muchas redes simultáneas. Además, puede agregar funcionalidad paralela más específica en la función de trabajo de un objeto transformer.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase transformer. En este ejemplo se crea un objeto transformer que multiplica cada valor int de entrada por 0.33 para generar un valor double como resultado. A continuación se recibe los valores transformados del mismo objeto transformer y se imprimen en la consola.
// 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;
}
Este ejemplo produce el siguiente resultado:
Para obtener un ejemplo completo que muestra cómo se usa la clase transformer, vea Cómo: Usar la clase transformer en una canalización de datos.
[Arriba]
Clase choice
La clase concurrency::choice selecciona el primer mensaje disponible de un conjunto de orígenes. La clase choice representa un mecanismo de flujo de control en lugar de un mecanismo de flujo de datos (en el tema Biblioteca de agentes asincrónicos se describen las diferencias entre el flujo de datos y el flujo de control).
La lectura de un objeto choice es similar a la llamada a la función WaitForMultipleObjects de la API de Windows cuando su parámetro bWaitAll está establecido en FALSE. Sin embargo, la clase choice enlaza los datos al propio evento en lugar de enlazarlos a un objeto de sincronización externo.
Por lo general, se usa la clase choice junto con la función concurrency::receive para dirigir el flujo de control de la aplicación. Utilice la clase choice si debe seleccionar entre búferes de mensajes con tipos diferentes. Utilice la clase single_assignment si debe seleccionar entre búferes de mensajes con el mismo tipo.
El orden en el que vincula los orígenes a un objeto choice es importante porque puede determinar qué mensaje se selecciona. Por ejemplo, supongamos que vincula varios búferes de mensajes que ya contienen un mensaje a un objeto choice. El objeto choice selecciona el mensaje del primer origen al que está vinculado. Después de vincular todos los orígenes, el objeto choice conserva el orden en el que cada origen recibe un mensaje.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase choice. En este ejemplo se usa la función concurrency::make_choice para crear un objeto choice que selecciona entre tres bloques de mensajes. A continuación se calculan varios números de Fibonacci y se almacena cada resultado en un bloque de mensajes diferente. Después, se imprime en la consola un mensaje basándose en la operación que finalizó primero.
// 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 ejemplo genera la siguiente salida de ejemplo:
Puesto que no se garantiza que la tarea que calcula el trigésimo quinto número de Fibonacci finalice primero, el resultado de este ejemplo puede variar.
En este ejemplo se usa el algoritmo concurrency::parallel_invoke para calcular los números de Fibonacci en paralelo. Para obtener más información sobre parallel_invoke, vea Algoritmos paralelos.
Para obtener un ejemplo completo que muestra cómo se usa la clase choice, vea Cómo: Seleccionar tareas completadas.
[Arriba]
Clases join y multitype_join
Las clases concurrency::join y concurrency::multitype_join permiten esperar hasta que cada miembro de un conjunto de orígenes recibe un mensaje. La clase join actúa en los objetos de origen que tienen un tipo de mensaje común. La clase multitype_join actúa en los objetos de origen que pueden tener diferentes tipos de mensaje.
La lectura de un objeto join o multitype_join es similar a la llamada a la función WaitForMultipleObjects de la API de Windows cuando su parámetro bWaitAll está establecido en TRUE. Sin embargo, al igual que en el objeto choice, los objetos join y multitype_join usan un mecanismo de evento que enlaza los datos al propio evento en lugar de enlazarlos a un objeto de sincronización externo.
La lectura de un objeto join produce un objeto std::vector. La lectura de un objeto multitype_join produce un objeto std::tuple. Los elementos aparecen en estos objetos en el mismo orden en que sus búferes de origen correspondientes se vinculan al objeto join o multitype_join. Como el orden en que vincula los búferes de origen a un objeto join o multitype_join está asociado con el orden de los elementos en el objeto vector o tuple resultante, se recomienda no desenlaza un búfer de origen existente de una unión. Si lo hace, puede producir un comportamiento no especificado.
Uniones expansivas y no expansivas
Las clases join y multitype_join admiten el concepto de uniones expansivas y no expansivas. Una unión expansiva acepta un mensaje de cada uno de sus orígenes a medida que los mensajes están disponibles, hasta que están disponibles todos los mensajes. Una unión no expansiva recibe los mensajes en dos fases. En primer lugar, una unión no expansiva espera hasta que cada uno de sus orígenes le ofrece un mensaje. En segundo lugar, después de que todos los mensajes de origen están disponibles, una unión no expansiva intenta reservar cada uno de esos mensajes. Si puede reservar cada mensaje, usa todos los mensajes y los propaga a su destino. De lo contrario, libera, o cancela, las reservas de mensajes y espera otra vez a que cada origen reciba un mensaje.
Las uniones expansivas funcionan mejor que las uniones no expansivas porque aceptan los mensajes de manera inmediata. Sin embargo, en raras ocasiones, las uniones expansivas pueden producir interbloqueos. Utilice una unión no expansiva si cuenta con varias uniones que contienen uno o más objetos de origen compartidos.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase join. En este ejemplo se usa la función concurrency::make_join para crear un objeto join que recibe de tres objetos single_assignment. En este ejemplo se calculan diversos números de Fibonacci, se almacena cada resultado en un objeto single_assignment diferente y, a continuación, se imprime en la consola cada resultado que el objeto join almacena. Este ejemplo es similar al ejemplo de la clase choice, excepto en que la clase join espera a que todos los bloques de mensajes de origen reciban un mensaje.
// 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;
}
Este ejemplo produce el siguiente resultado:
En este ejemplo se usa el algoritmo concurrency::parallel_invoke para calcular los números de Fibonacci en paralelo. Para obtener más información sobre parallel_invoke, vea Algoritmos paralelos.
Para obtener ejemplos completos que muestran cómo se usa la clase join, vea Cómo: Seleccionar tareas completadas y Tutorial: Usar la clase join para evitar un interbloqueo.
[Arriba]
Clase timer
La clase concurrency::timer actúa como origen de los mensajes. Un objeto timer envía un mensaje a un destino una vez transcurrido un período de tiempo especificado. La clase timer resulta útil si debe retrasar el envío de un mensaje o si desea enviar un mensaje de manera periódica.
La clase timer envía su mensaje a un único destino. Si establece el parámetro _PTarget del constructor en NULL, puede especificar el destino más adelante mediante un llamada al método concurrency::ISource::link_target.
Un objeto timer puede ser repetitivo o no repetitivo. Para crear un temporizador repetitivo, pase el valor true al parámetro _Repeating al llamar al constructor. De lo contrario, pase false al parámetro _Repeating para crear un temporizador no repetitivo. Si el temporizador es repetitivo, envía el mismo mensaje a su destino después de cada intervalo.
La Biblioteca de agentes crea los objetos timer en el estado no iniciado. Para iniciar un objeto de temporizador, llame al método concurrency::timer::start. Para detener un objeto timer, destrúyalo o llame al método concurrency::timer::stop. Para pausar un temporizador repetitivo, llame al método concurrency::timer::pause.
Ejemplo
En el ejemplo siguiente se muestra la estructura básica de cómo trabajar con la clase timer. En el ejemplo se usan objetos timer y call para informar sobre el progreso de una operación larga.
// 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 ejemplo genera la siguiente salida de ejemplo:
Para obtener un ejemplo completo que muestra cómo se usa la clase timer, vea Cómo: Enviar un mensaje a intervalos periódicos.
[Arriba]
Filtrado de mensajes
Cuando crea un objeto de bloque de mensajes, puede proporcionar una función de filtro que determina si el bloque de mensajes acepta o rechaza un mensaje. Una función de filtro resulta útil para garantizar que un bloque de mensajes recibe solo ciertos valores.
En el ejemplo siguiente se muestra cómo crear un objeto unbounded_buffer que usa una función de filtro para aceptar solo números pares. El objeto unbounded_buffer rechaza los números impares y, por tanto, no propaga números impares a sus bloques 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;
}
}
Este ejemplo produce el siguiente resultado:
Una función de filtro puede ser una función lambda, un puntero a función o un objeto de función. Cada función de filtro toma uno de los formatos siguientes.
Para eliminar la copia innecesaria de datos, use el segundo formato cuando tenga un tipo agregado que se propaga por valor.
El filtrado de mensajes admite el modelo de programación de flujo de datos, en el que los componentes realizan cálculos cuando reciben datos. Para obtener ejemplos que usan funciones de filtro para controlar el flujo de datos en una red de paso de mensajes, vea Cómo: Utilizar un filtro de bloque de mensaje, Tutorial: Crear un agente de flujo de datos y Tutorial: Crear una red de procesamiento de imagen.
[Arriba]
Reserva de mensajes
La reserva de mensajes permite a un bloque de mensajes reservar un mensaje para su uso posterior. Normalmente, la reserva de mensajes no se usa directamente. Sin embargo, entender la reserva de mensajes puede ayudarle a entender mejor el comportamiento de algunos tipos predefinidos de bloques de mensajes.
Considere las uniones no expansivas y expansivas. Ambas usan la reserva de mensajes para reservar mensajes para su uso posterior. Como se ha descrito anteriormente, una unión no expansiva recibe mensajes en dos fases. Durante la primera fase, un objeto join no expansivo espera que cada uno de sus orígenes reciba un mensaje. Una unión no expansiva después intenta reservar cada uno de esos mensajes. Si puede reservar cada mensaje, usa todos los mensajes y los propaga a su destino. De lo contrario, libera, o cancela, las reservas de mensajes y espera otra vez a que cada origen reciba un mensaje.
Una unión expansiva, que también lee mensajes de entrada de varios orígenes, usa la reserva de mensajes para leer mensajes adicionales mientras espera recibir un mensaje de cada origen. Por ejemplo, considere una unión expansiva que recibe mensajes de los bloques de mensajes A y B. Si la unión expansiva recibe dos mensajes de B pero aún no ha recibido un mensaje de A, la unión expansiva guarda el identificador de mensaje único para el segundo mensaje de B. Una vez que la unión expansiva recibe un mensaje de A y propaga out estos mensajes, usa el identificador de mensaje guardado para ver si el segundo mensaje de B sigue estando disponible.
Puede usar la reserva de mensajes al implementar sus propios tipos de bloques de mensajes. Para obtener un ejemplo sobre cómo crear un tipo de bloque de mensajes personalizado, vea Tutorial: Crear un bloque de mensajes personalizado.
[Arriba]