mirror of
https://github.com/boostorg/asio.git
synced 2026-02-24 02:12:12 +00:00
This is no longer an experimental facility. The names as_tuple and as_tuple_t have been temporarily retained as deprecated entities under the asio::experimental namespace, for backwards compatibility.
240 lines
8.8 KiB
Plaintext
240 lines
8.8 KiB
Plaintext
[/
|
|
/ Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
|
/
|
|
/ Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
/ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
/]
|
|
|
|
[section:cpp20_coroutines C++20 Coroutines Support]
|
|
|
|
Support for C++20 Coroutines is provided via the [link boost_asio.reference.awaitable
|
|
`awaitable`] class template, the [link boost_asio.reference.use_awaitable_t
|
|
`use_awaitable`] completion token, and the [link boost_asio.reference.co_spawn
|
|
`co_spawn()`] function. These facilities allow programs to implement
|
|
asynchronous logic in a synchronous manner, in conjunction with the `co_await`
|
|
keyword, as shown in the following example:
|
|
|
|
boost::asio::co_spawn(executor, echo(std::move(socket)), boost::asio::detached);
|
|
|
|
// ...
|
|
|
|
boost::asio::awaitable<void> echo(tcp::socket socket)
|
|
{
|
|
try
|
|
{
|
|
char data[1024];
|
|
for (;;)
|
|
{
|
|
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
|
|
co_await async_write(socket, boost::asio::buffer(data, n), boost::asio::use_awaitable);
|
|
}
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
std::printf("echo Exception: %s\n", e.what());
|
|
}
|
|
}
|
|
|
|
The first argument to `co_spawn()` is an [link boost_asio.reference.Executor1
|
|
executor] that determines the context in which the coroutine is permitted to
|
|
execute. For example, a server's per-client object may consist of multiple
|
|
coroutines; they should all run on the same `strand` so that no explicit
|
|
synchronisation is required.
|
|
|
|
The second argument is an [link boost_asio.reference.awaitable `awaitable<R>`],
|
|
that is the result of the coroutine's entry point function, and in the above
|
|
example is the result of the call to `echo`. (Alternatively, this argument can
|
|
be a function object that returns the [link boost_asio.reference.awaitable
|
|
`awaitable<R>`].) The template parameter `R` is the type of return value
|
|
produced by the coroutine. In the above example, the coroutine returns `void`.
|
|
|
|
The third argument is a completion token, and this is used by `co_spawn()` to
|
|
produce a completion handler with signature `void(std::exception_ptr, R)`. This
|
|
completion handler is invoked with the result of the coroutine once it has
|
|
finished. In the above example we pass a completion token type, [link
|
|
boost_asio.reference.detached `boost::asio::detached`], which is used to explicitly ignore
|
|
the result of an asynchronous operation.
|
|
|
|
In this example the body of the coroutine is implemented in the `echo`
|
|
function. When the `use_awaitable` completion token is passed to an
|
|
asynchronous operation, the operation's initiating function returns an
|
|
`awaitable` that may be used with the `co_await` keyword:
|
|
|
|
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
|
|
|
|
Where an asynchronous operation's handler signature has the form:
|
|
|
|
void handler(boost::system::error_code ec, result_type result);
|
|
|
|
the resulting type of the `co_await` expression is `result_type`. In the
|
|
`async_read_some` example above, this is `size_t`. If the asynchronous
|
|
operation fails, the `error_code` is converted into a `system_error` exception
|
|
and thrown.
|
|
|
|
Where a handler signature has the form:
|
|
|
|
void handler(boost::system::error_code ec);
|
|
|
|
the `co_await` expression produces a `void` result. As above, an error is
|
|
passed back to the coroutine as a `system_error` exception.
|
|
|
|
[heading Error Handling]
|
|
|
|
To perform explicit error handling, rather than the default exception-throwing
|
|
behaviour, use the [link boost_asio.reference.as_tuple `as_tuple`] or
|
|
[link boost_asio.reference.redirect_error `redirect_error`] completion token adapters.
|
|
|
|
The `as_tuple` completion token adapter packages the completion
|
|
handler arguments into a single tuple, which is then returned as the result of
|
|
the awaited operation. For example:
|
|
|
|
boost::asio::awaitable<void> echo(tcp::socket socket)
|
|
{
|
|
char data[1024];
|
|
for (;;)
|
|
{
|
|
std::tuple<boost::system::error_code, std::size_t> result =
|
|
co_await socket.async_read_some(boost::asio::buffer(data),
|
|
boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
if (!std::get<0>(result))
|
|
{
|
|
// success
|
|
}
|
|
|
|
// ...
|
|
}
|
|
}
|
|
|
|
The result can also be captured directly into a structured binding:
|
|
|
|
boost::asio::awaitable<void> echo(tcp::socket socket)
|
|
{
|
|
char data[1024];
|
|
for (;;)
|
|
{
|
|
auto [ec, n] = co_await socket.async_read_some(
|
|
boost::asio::buffer(data), boost::asio::as_tuple(boost::asio::use_awaitable));
|
|
if (!ec)
|
|
{
|
|
// success
|
|
}
|
|
|
|
// ...
|
|
}
|
|
}
|
|
|
|
Alternatively, the `redirect_error` completion token adapter may be used to
|
|
capture the error into a supplied `error_code` variable:
|
|
|
|
boost::asio::awaitable<void> echo(tcp::socket socket)
|
|
{
|
|
char data[1024];
|
|
for (;;)
|
|
{
|
|
boost::system::error_code ec;
|
|
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data),
|
|
boost::asio::redirect_error(boost::asio::use_awaitable, ec));
|
|
if (!ec)
|
|
{
|
|
// success
|
|
}
|
|
|
|
// ...
|
|
}
|
|
}
|
|
|
|
[heading Coroutines and Per-Operation Cancellation]
|
|
|
|
All threads of execution created by `co_spawn` have a cancellation state that
|
|
records the current state of any cancellation requests made to the coroutine.
|
|
To access this state, use [link boost_asio.reference.this_coro__cancellation_state
|
|
`this_coro::cancellation_state`] as follows:
|
|
|
|
boost::asio::awaitable<void> my_coroutine()
|
|
{
|
|
boost::asio::cancellation_state cs
|
|
= co_await boost::asio::this_coro::cancellation_state;
|
|
|
|
// ...
|
|
|
|
if (cs.cancelled() != boost::asio::cancellation_type::none)
|
|
// ...
|
|
}
|
|
|
|
When first created by `co_spawn`, the thread of execution has a cancellation
|
|
state that supports `cancellation_type::terminal` values only. To change the
|
|
cancellation state, call [link
|
|
boost_asio.reference.this_coro__reset_cancellation_state
|
|
`this_coro::reset_cancellation_state`].
|
|
|
|
By default, continued execution of a cancelled coroutine will trigger an
|
|
exception from any subsequent `co_await` of an `awaitable<>` object. This
|
|
behaviour can be changed by using [link
|
|
boost_asio.reference.this_coro__throw_if_cancelled `this_coro::throw_if_cancelled`].
|
|
|
|
[heading Co-ordinating Parallel Coroutines]
|
|
|
|
[note This is an experimental feature.]
|
|
|
|
The logical operators `||` and `&&` have been overloaded for `awaitable<>`, to
|
|
allow coroutines to be trivially awaited in parallel.
|
|
|
|
When awaited using `&&`, the `co_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)
|
|
);
|
|
|
|
Following completion of a `&&` operation, the results of all operations are
|
|
concatenated into a tuple. In the above example, the first `size_t` represents
|
|
the non-exceptional component of the `async_read` result, and the second
|
|
`size_t` is the result of the `async_write`.
|
|
|
|
When awaited using `||`, the `co_await` expression waits until either
|
|
operation succeeds. 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)
|
|
);
|
|
|
|
Following completion of a `||` operation, the result of the first operation to
|
|
complete non-exceptionally is placed into a `std::variant`. The active index
|
|
of the variant reflects which of the operations completed first.
|
|
In the above example, index `0` corresponds to the `async_read` operation.
|
|
|
|
These operators may be enabled by adding the `#include`:
|
|
|
|
#include <boost/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;
|
|
|
|
[heading See Also]
|
|
|
|
[link boost_asio.reference.co_spawn co_spawn],
|
|
[link boost_asio.reference.detached detached],
|
|
[link boost_asio.reference.as_tuple as_tuple],
|
|
[link boost_asio.reference.redirect_error redirect_error],
|
|
[link boost_asio.reference.awaitable awaitable],
|
|
[link boost_asio.reference.use_awaitable_t use_awaitable_t],
|
|
[link boost_asio.reference.use_awaitable use_awaitable],
|
|
[link boost_asio.reference.this_coro__executor this_coro::executor],
|
|
[link boost_asio.reference.experimental__coro experimental::coro],
|
|
[link boost_asio.examples.cpp17_examples.coroutines_ts_support Coroutines examples],
|
|
[link boost_asio.overview.composition.coro Resumable C++20 Coroutines],
|
|
[link boost_asio.overview.composition.spawn Stackful Coroutines],
|
|
[link boost_asio.overview.composition.coroutine Stackless Coroutines].
|
|
|
|
[endsect]
|