forked from boostorg/mqtt5
Add a chapter on asio compliance - executors
Summary: related to T12804 Reviewers: ivica Reviewed By: ivica Subscribers: miljen, iljazovic Differential Revision: https://repo.mireo.local/D28496
This commit is contained in:
@ -116,6 +116,7 @@
|
|||||||
[include 04_maintaining_a_stable_connection.qbk]
|
[include 04_maintaining_a_stable_connection.qbk]
|
||||||
[include 05_optimising_communication.qbk]
|
[include 05_optimising_communication.qbk]
|
||||||
[include 06_disconnecting_the_client.qbk]
|
[include 06_disconnecting_the_client.qbk]
|
||||||
|
[include 07_asio_compliance.qbk]
|
||||||
|
|
||||||
[include 10_examples.qbk]
|
[include 10_examples.qbk]
|
||||||
|
|
||||||
|
14
doc/qbk/07_asio_compliance.qbk
Normal file
14
doc/qbk/07_asio_compliance.qbk
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[section:asio_compliance Compliance with Boost.Asio]
|
||||||
|
|
||||||
|
Every asynchronous operation in __Asio__ has associated characteristics that specify their behaviour.
|
||||||
|
|
||||||
|
* An *allocator* determines how the asynchronous operations allocate memory resources.
|
||||||
|
* A *cancellation slot* determines how the asynchronous operations support cancellation.
|
||||||
|
* An *executor* determines the queuing and execution strategy for completion handlers.
|
||||||
|
|
||||||
|
This section expands further into the roles of allocators, cancellation slots, and [link async_mqtt5.asio_compliance.executors Executors],
|
||||||
|
highlighting their integration and usage within the __Client__.
|
||||||
|
|
||||||
|
[include 08_executors.qbk]
|
||||||
|
|
||||||
|
[endsect] [/asio_compliance]
|
141
doc/qbk/08_executors.qbk
Normal file
141
doc/qbk/08_executors.qbk
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
[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]
|
Reference in New Issue
Block a user