The logical operators || and && have been overloaded for awaitable<>, to
allow coroutines to be trivially awaited in parallel.
When awaited using &&, the await expression waits until both operations
have completed successfully. As a "short-circuit" evaluation, if one
operation fails with an exception, the other is immediately cancelled.
For example:
std::tuple<std::size_t, std::size_t> results =
co_await (
async_read(socket, input_buffer, use_awaitable)
&& async_write(socket, output_buffer, use_awaitable)
);
When awaited using ||, the await expression waits until either operation
succceeds. As a "short-circuit" evaluation, if one operation succeeds
without throwing an exception, the other is immediately cancelled. For
example:
std::variant<std::size_t, std::monostate> results =
co_await (
async_read(socket, input_buffer, use_awaitable)
|| timer.async_wait(use_awaitable)
);
The operators may be enabled by adding the #include:
#include <asio/experimental/awaitable_operators.hpp>
and then bringing the contents of the experimental::awaitable_operators
namespace into scope:
using namespace boost::asio::experimental::awaitable_operators;
The experimental::promise type allows eager execution and
synchronisation of async operations.
auto promise = async_read(
stream, asio::buffer(my_buffer),
asio::experimental::use_promise);
... do other stuff while the read is going on ...
promise.async_wait( // completion the operation
[](error_code ec, std::size_t bytes_read)
{
...
});
Promises can be safely disregarded if the result is no longer required.
Different operations can be combined to either wait for all to complete
or for one to complete (and cancel the rest). For example, to wait for
one to complete:
auto timeout_promise =
timer.async_wait(
asio::experimental::use_promise);
auto read_promise = async_read(
stream, asio::buffer(my_buffer),
asio::experimental::use_promise);
auto promise =
asio::experimental::promise<>::race(
timeout_promise, read_promise);
promise.async_wait(
[](std::variant<error_code, std::tuple<error_code, std::size_t>> v)
{
if (v.index() == 0) {} //timed out
else if (v.index() == 1) // completed in time
});
or to wait for all to complete:
auto write_promise = async_write(
stream, asio::buffer(my_write_buffer),
asio::experimental::use_promise);
auto read_promise = async_read(
stream, asio::buffer(my_buffer),
asio::experimental::use_promise);
auto promise =
asio::experimental::promise<>::all(
write_promise, read_promise);
promise.async_wait(
[](std::tuple<error_code, std::size_t> write_result,
std::tuple<error_code, std::size_t> read_result)
{
});
The experimental::deferred completion token takes a call to an
asynchronous operation's initiating function and turns it into a
function object that accepts a completion token. For example:
auto deferred_op =
timer.async_wait(
boost::asio::experimental::deferred);
...
std::move(deferred_op)(
[](std::error_code ec){ ... });
or
auto deferred_op =
timer.async_wait(
boost::asio::experimental::deferred);
...
std::future<void> =
std::move(deferred_op)(
boost::asio::use_future);
The deferred token also supports chaining, to create simple
compositions:
auto deferred_op =
timer.async_wait(
boost::asio::experimental::deferred(
[&](std::error_code ec)
{
timer.expires_after(
std::chrono::seconds(1));
return timer.async_wait(
boost::asio::experimental::deferred);
});
...
std::future<void> = std::move(deferred_op)(boost::asio::use_future);
The prepend completion token adapter can be used to pass additional
before the existing completion handler arguments. For example:
timer.async_wait(
boost::asio::experimental::prepend(
[](int i, std::error_code ec)
{
// ...
},
42)
);
std::future<std::tuple<int, std::error_code>> f = timer.async_wait(
boost::asio::experimental::prepend(
boost::asio::use_future,
42
)
);
The append completion token adapter can be used to pass additional
completion handler arguments. For example:
timer.async_wait(
boost::asio::experimental::append(
[](std::error_code ec, int i)
{
// ...
},
42)
);
std::future<int> f = timer.async_wait(
boost::asio::experimental::append(
asio::use_future,
42
)
);
The as_tuple completion token adapter can be used to specify that the
completion handler arguments should be combined into a single tuple
argument.
The as_tuple adapter may be used in conjunction with use_awaitable and
structured bindings as follows:
auto [e, n] = co_await socket.async_read_some(
asio::buffer(data), as_tuple(use_awaitable));
Alternatively, it may be used as a default completion token like so:
using default_token = as_tuple_t<use_awaitable_t<>>;
using tcp_socket = default_token::as_default_on_t<tcp::socket>;
// ...
awaitable<void> do_read(tcp_socket socket)
{
// ...
auto [e, n] = co_await socket.async_read_some(asio::buffer(data));
// ...
}