Files
boost_mqtt5/doc/qbk/10_executors.qbk
T
Korina Šimičević c7a4fcd507 Add a chapter on asio compliance - allocators
Summary: related to T12804

Reviewers: ivica

Reviewed By: ivica

Subscribers: miljen, iljazovic

Differential Revision: https://repo.mireo.local/D28543
2024-03-21 08:31:23 +01:00

142 lines
5.6 KiB
Plaintext

[section:executors Executors]
Every asynchronous operation has an associated executor that determines how the completion handlers are queued and run.
Asynchronous operations use the associated executor to track the existence of the work of asynchronous tasks,
schedule the completion handlers for execution,
and prevent re-entrant execution of the completion handlers to avoid recursion and potential stack overflow issues.
Every asynchronous operation within the __Client__ is defined as a composed operation.
This implies that each `async_xxx` operation is a sequence consisting of an initiating function,
a series of intermediate asynchronous operations, and a final completion handler.
Upon creating an instance of the __Client__, it is necessary to provide an executor or an __ExecutionContext__.
The specified executor (or __ExecutionContext__'s executor) will become the default executor associated with the __Client__
and will be used for the execution of all the intermediate operations and a final completion handler for all asynchronous operations
that have not bound an executor.
If an executor is bound to an asynchronous operation, that executor will be used instead.
In this context, the [refmem mqtt_client async_run] operation is particularly important.
It starts the __Client__, which initiates a series of internal asynchronous operations, all of which need an executor.
If the [refmem mqtt_client async_run] is called with a completion handler that has an associated executor,
then all the internal asynchronous operations will also be associated with the same executor.
Otherwise, the default executor from the __Client__'s construction will be used instead.
[note
If the [refmem mqtt_client async_run]'s completion handler has an associated executor,
*the associated executor will become the new default associated executor* instead of the executor provided in the constructor.
]
[important
The same executor *must* execute [refmem mqtt_client async_run] and all the subsequent async_xxx operations.
]
The following examples will demonstrate the previously described interactions.
[heading Example 1: using the constructor's executor as the default associated executor]
In this code snippet, the __Client__ is constructed with a strand
without explicitly associating an executor with the completion handler of [refmem mqtt_client async_run].
Consequently, the __Client__ adopts the strand as its new default executor,
which is used to execute the [refmem mqtt_client async_publish] operation.
```
int main() {
boost::asio::io_context ioc;
// Construct the Client with a strand.
auto strand = boost::asio::make_strand(ioc.get_executor());
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(strand);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// This asynchronous operation will use the default associated executor,
// which is the strand with which the Client is constructed.
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&client, &strand](async_mqtt5::error_code /* ec */) {
assert(strand.running_in_this_thread());
client.cancel();
}
);
ioc.run();
}
```
[heading Example 2: binding an executor to async_run's handler overrides the default associated executor]
In this code snippet, the __Client__ is constructed with __IOC__'s executor
with explicitly associating an executor (strand) with the completion handler of [refmem mqtt_client async_run].
Consequently, the __Client__ adopts the strand as its new default executor,
which is used to execute the [refmem mqtt_client async_publish] operation.
```
int main() {
boost::asio::io_context ioc;
// Create the Client with io_context's executor.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc.get_executor());
auto strand = boost::asio::make_strand(ioc.get_executor());
client.brokers("<your-mqtt-broker>", 1883)
// Bind the strand to async_run's completion handler.
// Strand is now the default associated executor.
.async_run(boost::asio::bind_executor(strand, boost::asio::detached));
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&client, &strand](async_mqtt5::error_code /* ec */) {
assert(strand.running_in_this_thread());
client.cancel();
}
);
ioc.run();
}
```
[heading Example 3: binding an executor to a async_xxx call]
In this code snippet, the __Client__ is constructed with __IOC__'s executor
without explicitly associating an executor with the completion handler of [refmem mqtt_client async_run].
The [refmem mqtt_client async_publish] operation bound the strand as the associated executor.
Therefore, all the intermediate operations and the final completion handler will be executed
on the strand.
[warning
This example only serves as a demonstration and should *not* be used.
It is *not* recommended that [refmem mqtt_client async_run] and other async_xxx operations execute on different executors!
]
```
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc.get_executor());
auto strand = boost::asio::make_strand(ioc.get_executor());
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
boost::asio::bind_executor(
strand,
[&client, &strand](async_mqtt5::error_code /* ec */) {
assert(strand.running_in_this_thread());
client.cancel();
}
)
);
ioc.run();
}
```
[endsect] [/executors]