通过 awaitable
类模板、use_awaitable
完成令牌和 co_spawn()
函数提供对 C++20 协程的支持。这些工具允许程序以同步方式实现异步逻辑,并结合 co_await
关键字,如下例所示
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)); co_await async_write(socket, boost::asio::buffer(data, n)); } } catch (std::exception& e) { std::printf("echo Exception: %s\n", e.what()); } }
co_spawn()
的第一个参数是一个 执行器,它决定了允许协程执行的上下文。例如,服务器的每个客户端对象可能由多个协程组成;它们都应该在同一个 strand
上运行,这样就不需要显式的同步。
第二个参数是一个 awaitable<R>
,它是协程入口点函数的结果,在上面的例子中是对 echo
的调用的结果。(或者,此参数可以是返回 awaitable<R>
的函数对象。)模板参数 R
是协程产生的返回值类型。在上面的例子中,协程返回 void
。
第三个参数是一个完成令牌,co_spawn()
使用它来生成一个完成处理程序,其签名为 void(std::exception_ptr, R)
。一旦协程完成,就会使用协程的结果调用此完成处理程序。在上面的例子中,我们传递了一个完成令牌类型 boost::asio::detached
,它用于显式地忽略异步操作的结果。
在此示例中,协程的主体在 echo
函数中实现。当调用异步操作而没有显式指定完成令牌时,将使用默认完成令牌 deferred
。这会导致操作的启动函数返回一个延迟的异步操作对象,该对象可以与 co_await
关键字一起使用
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data));
或者,我们可以指定 use_awaitable
完成令牌
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
使用这些完成令牌中的任何一个,当异步操作的处理程序签名具有以下形式时
void handler(boost::system::error_code ec, result_type result);
co_await
表达式的结果类型是 result_type
。在上面的 async_read_some
示例中,这是 size_t
。如果异步操作失败,则 error_code
将转换为 system_error
异常并抛出。
当处理程序签名具有以下形式时
void handler(boost::system::error_code ec);
co_await
表达式产生 void
结果。与上面一样,错误作为 system_error
异常传递回协程。
要执行显式错误处理,而不是默认的抛出异常行为,请使用 as_tuple
或 redirect_error
完成令牌适配器。
as_tuple
完成令牌适配器将完成处理程序参数打包到一个元组中,然后作为等待操作的结果返回。例如
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); if (!std::get<0>(result)) { // success } // ... } }
或者,如果显式指定 use_awaitable
完成令牌
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 } // ... } }
结果也可以直接捕获到结构化绑定中
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); if (!ec) { // success } // ... } }
或者,可以使用 redirect_error
完成令牌适配器将错误捕获到提供的 error_code
变量中
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(ec)); if (!ec) { // success } // ... } }
由 co_spawn
创建的所有执行线程都有一个取消状态,该状态记录对协程发出的任何取消请求的当前状态。要访问此状态,请使用 this_coro::cancellation_state
,如下所示
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) // ... }
当首次由 co_spawn
创建时,执行线程的取消状态仅支持 cancellation_type::terminal
值。要更改取消状态,请调用 this_coro::reset_cancellation_state
。
默认情况下,继续执行已取消的协程将从任何后续的 co_await
的 awaitable<>
对象触发异常。可以通过使用 this_coro::throw_if_cancelled
来更改此行为。
![]() |
注意 |
---|---|
这是一个实验性功能。 |
逻辑运算符 ||
和 &&
已为 awaitable<>
重载,以允许并行等待协程。
当使用 &&
等待时,co_await
表达式会等待直到两个操作都成功完成。作为“短路”求值,如果一个操作因异常而失败,则另一个操作会立即取消。例如
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) );
在 &&
操作完成后,所有操作的结果都连接到一个元组中。在上面的例子中,第一个 size_t
表示 async_read
结果的非异常组件,第二个 size_t
是 async_write
的结果。
当使用 ||
等待时,co_await
表达式会等待直到任一操作成功。作为“短路”求值,如果一个操作成功完成且没有抛出异常,则另一个操作会立即取消。例如
std::variant<std::size_t, std::monostate> results = co_await ( async_read(socket, input_buffer, use_awaitable) || timer.async_wait(use_awaitable) );
在 ||
操作完成后,第一个非异常完成的操作的结果将放入 std::variant
中。变体的活动索引反映了哪个操作首先完成。在上面的示例中,索引 0
对应于 async_read
操作。
可以通过添加 #include
来启用这些运算符
#include <boost/asio/experimental/awaitable_operators.hpp>
然后将 experimental::awaitable_operators
命名空间的内容引入作用域
using namespace boost::asio::experimental::awaitable_operators;
注意:要使用这些运算符,我们必须显式指定 use_awaitable
完成令牌。
co_composed
模板有助于使用 C++20 协程轻量级实现用户定义的异步操作。以下示例说明了一个简单的异步操作,该操作根据协程实现回显协议
template <typename CompletionToken> auto async_echo(tcp::socket& socket, CompletionToken&& token) { return boost::asio::async_initiate< CompletionToken, void(boost::system::error_code)>( boost::asio::co_composed< void(boost::system::error_code)>( [](auto state, tcp::socket& socket) -> void { try { state.throw_if_cancelled(true); state.reset_cancellation_state( boost::asio::enable_terminal_cancellation()); for (;;) { char data[1024]; std::size_t n = co_await socket.async_read_some( boost::asio::buffer(data)); co_await boost::asio::async_write(socket, boost::asio::buffer(data, n)); } } catch (const boost::system::system_error& e) { co_return {e.code()}; } }, socket), token, std::ref(socket)); }
co_spawn, detached, as_tuple, redirect_error, awaitable, use_awaitable_t, use_awaitable, this_coro::executor, co_composed, 协程示例, 可恢复的 C++20 协程, Stackful 协程, Stackless 协程.