单线程下如何用co_spawn结合co_await实现task_main等待task_sub完成?
问题:Asio单线程下等待所有子协程完成的正确实现
需求说明
使用Asio 1.31.0,需实现以下逻辑:
- 在
task_main()函数开头创建多个task_sub()协程 - 单线程环境下,
task_main()必须等待所有task_sub()执行完毕后再退出 task_sub()必须在task_main()内部创建,二者并非同级协程
尝试过的方案及问题
1. 使用use_future
无法通过co_await直接等待std::future,调用wait()会同步阻塞主事件循环,导致线程卡住:
awaitable<void> task_main() { printf("task_main begin\n"); // 创建并执行子协程 std::future<void> task1 = co_spawn(co_await this_coro::executor, task_sub(), use_future); std::future<void> task2 = co_spawn(co_await this_coro::executor, task_sub(), use_future); // 主循环逻辑 // TODO something // 等待子协程完成 // co_await task1; // 无法直接co_await std::future // co_await task2; // 此处会同步阻塞主循环 task1.wait(); task2.wait(); printf("task_main end\n"); }
2. 使用channel
可以实现需求,但需要为每个子协程单独创建通道,代码写法冗余且不够优雅:
//... #include <asio/experimental/channel.hpp> using exit_channel = asio::experimental::channel<void(asio::error_code)>; awaitable<void> task_sub(exit_channel& channel) { // 子协程业务逻辑 // TODO something // 通知主协程自身完成 co_await channel.async_send(error_code{}); } awaitable<void> task_main() { printf("task_main begin\n"); auto ctx = co_await this_coro::executor; // 创建子协程及对应通道 exit_channel task1_channel(ctx, 1); exit_channel task2_channel(ctx, 1); co_spawn(ctx, task_sub(task1_channel), detached); co_spawn(ctx, task_sub(task2_channel), detached); // 主循环逻辑 // TODO something // 等待所有子协程完成 co_await task1_channel.async_receive(); co_await task2_channel.async_receive(); printf("task_main end\n"); }
3. 使用use_awaitable(错误用法)
直接co_spawn传入use_awaitable但未立即处理返回的awaitable时,子协程不会启动,不符合需求。
正确实现方案
方案1:容器存储awaitable批量等待
核心逻辑:
- 用
co_spawn启动子协程时,将返回的awaitable<void>存入容器 - 在需要等待的位置,循环
co_await所有子协程的awaitable对象
这种方式既保证子协程立即启动,又能非阻塞等待所有任务完成,代码简洁。
示例代码
#include <asio.hpp> #include <vector> #include <cstdio> using namespace asio; awaitable<void> task_sub() { printf("task_sub start\n"); // 模拟子协程业务逻辑(异步延迟) co_await steady_timer(co_await this_coro::executor, std::chrono::seconds(1)).async_wait(use_awaitable); printf("task_sub end\n"); } awaitable<void> task_main() { printf("task_main begin\n"); auto executor = co_await this_coro::executor; std::vector<awaitable<void>> sub_tasks; // 创建并启动多个子协程,存储返回的awaitable sub_tasks.emplace_back(co_spawn(executor, task_sub(), use_awaitable)); sub_tasks.emplace_back(co_spawn(executor, task_sub(), use_awaitable)); // 主循环业务逻辑 printf("task_main running main loop\n"); // TODO: 执行主循环的异步操作 // 等待所有子协程完成 for (auto& task : sub_tasks) { co_await std::move(task); } printf("task_main end\n"); } int main() { io_context ctx; co_spawn(ctx, task_main(), detached); ctx.run(); return 0; }
方案2:使用when_all并行等待
如果需要并行等待所有子协程(无需逐个等待),可以使用Asio的experimental::when_all工具(需包含<asio/experimental/when_all.hpp>),进一步简化代码:
示例代码
#include <asio.hpp> #include <asio/experimental/when_all.hpp> #include <cstdio> using namespace asio; awaitable<void> task_sub() { printf("task_sub start\n"); co_await steady_timer(co_await this_coro::executor, std::chrono::seconds(1)).async_wait(use_awaitable); printf("task_sub end\n"); } awaitable<void> task_main() { printf("task_main begin\n"); auto executor = co_await this_coro::executor; // 创建并启动子协程 auto task1 = co_spawn(executor, task_sub(), use_awaitable); auto task2 = co_spawn(executor, task_sub(), use_awaitable); // 主循环业务逻辑 printf("task_main running main loop\n"); // TODO: 主循环异步操作 // 并行等待所有子协程完成 co_await asio::experimental::when_all(std::move(task1), std::move(task2)); printf("task_main end\n"); } int main() { io_context ctx; co_spawn(ctx, task_main(), detached); ctx.run(); return 0; }
方案说明
- 子协程立即启动:
co_spawn搭配use_awaitable时,子协程会在调用co_spawn时立即执行,满足“在task_main开头创建并启动”的需求 - 非阻塞等待:通过
co_await等待awaitable对象,不会阻塞主事件循环,Asio会自动调度其他就绪任务 - 代码简洁:无需额外同步对象,仅用容器或
when_all即可实现批量等待
内容的提问来源于stack exchange,提问作者kaho J
相关产品推荐
相关产品推荐

