Support for C++20 Coroutines is provided via the awaitable
class template, the use_awaitable
completion token, and the 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:
asio::co_spawn(executor, echo(std::move(socket)), asio::detached); // ... asio::awaitable<void> echo(tcp::socket socket) { try { char data[1024]; for (;;) { std::size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable); co_await async_write(socket, asio::buffer(data, n), asio::use_awaitable); } } catch (std::exception& e) { std::printf("echo Exception: %s\n", e.what()); } }
The first argument to co_spawn()
is an 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 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 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,
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(asio::buffer(data), asio::use_awaitable);
Where an asynchronous operation's handler signature has the form:
void handler(asio::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(asio::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.
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 this_coro::cancellation_state
as follows:
asio::awaitable<void> my_coroutine() { asio::cancellation_state cs = co_await asio::this_coro::cancellation_state; // ... if (cs.cancelled() != 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 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 this_coro::throw_if_cancelled
.
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 <asio/experimental/awaitable_operators.hpp>
and then bringing the contents of the experimental::awaitable_operators
namespace into scope:
using namespace asio::experimental::awaitable_operators;
Note | |
---|---|
This is an experimental feature. |
The coro
type is a C++20 coroutine primitive for resumable functions, with the ability
to combine both asynchronous waiting (co_await
) and yielding
(co_yield
) into a single, stateful control flow. For example:
#include <asio.hpp> #include <asio/experimental/coro.hpp> using asio::ip::tcp; asio::experimental::coro<std::string> reader(tcp::socket& sock) { std::string buf; while (sock.is_open()) { std::size_t n = co_await asio::async_read_until( sock, asio::dynamic_buffer(buf), '\n', asio::experimental::use_coro); co_yield buf.substr(0, n); buf.erase(0, n); } } asio::awaitable<void> consumer(tcp::socket sock) { auto r = reader(sock); auto msg1 = co_await r.async_resume(asio::use_awaitable); std::cout << "Message 1: " << msg1.value_or("\n"); auto msg2 = co_await r.async_resume(asio::use_awaitable); std::cout << "Message 2: " << msg2.value_or("\n"); } asio::awaitable<void> listen(tcp::acceptor& acceptor) { for (;;) { co_spawn( acceptor.get_executor(), consumer(co_await acceptor.async_accept(asio::use_awaitable)), asio::detached); } } int main() { asio::io_context ctx; tcp::acceptor acceptor(ctx, {tcp::v4(), 54321}); co_spawn(ctx, listen(acceptor), asio::detached); ctx.run(); }
co_spawn, detached, redirect_error, awaitable, use_awaitable_t, use_awaitable, this_coro::executor, experimental::coro, Coroutines examples, Stackful Coroutines, Stackless Coroutines.