如何基于Boost.Asio的awaitable实现whenAll等任务原语
Boost.Asio
awaitable<T> 协程任务原语实现方案 官方原生支持(推荐优先使用)
Boost.Asio 从1.80版本开始已经在实验性命名空间下提供了和.NET Task.WhenAll/Task.WhenAny完全对应的能力,不需要从零手写实现:
- 头文件引入:只需要包含
boost/asio/experimental/awaitable_operators.hpp即可使用所有相关原语 when_all(等待所有任务完成):
支持两种调用方式,一种是通过重载的&&运算符拼接多个awaitable,另一种是直接调用可变参数模板函数when_all。传入的所有任务会自动被投递到当前协程绑定的io_context执行器上并行调度,所有任务完成后会返回包含所有任务返回值的元组,任务抛出的异常会直接向外传播。
示例代码:#include <boost/asio/experimental/awaitable_operators.hpp> using namespace boost::asio::experimental::awaitable_operators; asio::awaitable<int> task1(); asio::awaitable<std::string> task2(); asio::awaitable<void> demo_when_all() { // 运算符写法 auto [num_ret, str_ret] = co_await (task1() && task2()); // 函数写法,支持任意数量任务 // auto all_results = co_await when_all(task1(), task2(), task3()); }when_any(等待任意任务完成):
对应提供了重载的||运算符和when_any函数,只要传入的任意一个awaitable执行完成,就会立刻恢复外层协程,剩余未完成的任务会收到取消信号自动终止。返回值是std::variant类型,按顺序存储所有可能的任务返回值类型,可以通过std::holds_alternative、std::get判断和获取第一个完成的任务结果。
示例代码:asio::awaitable<void> demo_when_any() { asio::steady_timer timeout(co_await asio::this_coro::executor, std::chrono::seconds(3)); auto ret = co_await (timeout.async_wait(asio::use_awaitable) || task1()); if (std::holds_alternative<int>(ret)) { // task1先完成 } else { // 超时先触发 } }taskFromResult/TaskCompletionSource对应能力:
直接返回已就绪结果的taskFromResult可以用asio::make_awaitable实现,传入执行器和要返回的值,就能得到一个立即就绪的awaitable<T>。
对应TaskCompletionSource的手动完成触发能力,可以通过自定义异步操作配合async_result实现,简单场景下用一个关联了执行器的promise即可,支持手动设置返回值、异常,触发等待的协程恢复。
旧版本Asio手动实现要点
如果使用1.80之前的Asio版本,没有内置上述原语,可以按以下逻辑自行实现:
- 并行调度的核心是不要在同一个协程内顺序
co_await多个任务(这种写法是串行执行),要把所有需要并行的任务通过asio::co_spawn投递到同一个io_context执行器上,由事件循环统一调度才能实现并行。 when_all实现:用原子变量维护剩余未完成任务的计数,预先分配好结果存储的内存,每个任务完成时把结果存入对应位置、计数减1,计数归0时恢复外层等待协程;如果需要异常快速失败逻辑,任意任务抛出异常时直接触发外层协程恢复,同时给其他未完成任务发送取消信号。when_any实现:用原子标记位记录是否已有任务完成,任意任务完成时先通过标记位做去重检查,第一个完成的任务负责保存结果、恢复外层协程,其余任务后续完成时直接丢弃结果即可,触发恢复的同时要给所有未完成任务传递停止信号,避免资源泄漏。- 注意所有协程的恢复操作必须投递到对应
io_context的执行器上执行,禁止跨线程直接resume协程,否则会触发数据竞争。
内容的提问来源于stack exchange,提问作者tripleslash
相关产品推荐
相关产品推荐

