![]() |
Home | Libraries | People | FAQ | More |
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:
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 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,
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.
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:
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 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 <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;
![]() |
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 <boost/asio/experimental/coro.hpp>
using boost::asio::ip::tcp;
boost::asio::experimental::coro<std::string> reader(tcp::socket& sock)
{
std::string buf;
while (sock.is_open())
{
std::size_t n = co_await boost::asio::async_read_until(
sock, boost::asio::dynamic_buffer(buf), '\n',
boost::asio::experimental::use_coro);
co_yield buf.substr(0, n);
buf.erase(0, n);
}
}
boost::asio::awaitable<void> consumer(tcp::socket sock)
{
auto r = reader(sock);
auto msg1 = co_await r.async_resume(boost::asio::use_awaitable);
std::cout << "Message 1: " << msg1.value_or("\n");
auto msg2 = co_await r.async_resume(boost::asio::use_awaitable);
std::cout << "Message 2: " << msg2.value_or("\n");
}
boost::asio::awaitable<void> listen(tcp::acceptor& acceptor)
{
for (;;)
{
co_spawn(
acceptor.get_executor(),
consumer(co_await acceptor.async_accept(boost::asio::use_awaitable)),
boost::asio::detached);
}
}
int main()
{
boost::asio::io_context ctx;
tcp::acceptor acceptor(ctx, {tcp::v4(), 54321});
co_spawn(ctx, listen(acceptor), boost::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.