如何使用Boost Asio实现恢复C++20协程的一次性事件类?
用Boost Asio实现C++20协程的一次性事件类(oneshot_event)
核心实现代码
针对需求,以下是一个线程安全的oneshot_event实现,支持单个或多个协程等待,调用raise()时立即恢复所有挂起的协程:
#include <boost/asio.hpp> #include <coroutine> #include <vector> #include <optional> namespace async = boost::asio; struct oneshot_event { // 传入执行器,确保所有操作在同一上下文(线程安全) explicit oneshot_event(async::any_io_executor ex) : executor_(std::move(ex)) {} // 触发事件,线程安全 void raise() { async::post(executor_, [this]() { if (!raised_) { raised_ = true; // 恢复所有等待的协程 for (auto coro : waiters_) { coro.resume(); } waiters_.clear(); } }); } // 等待事件,可被co_await async::awaitable<void> wait() { struct awaiter { oneshot_event& event_; std::coroutine_handle<> coro_; // 检查事件是否已触发,避免不必要的挂起 bool await_ready() const noexcept { return event_.raised_; } // 挂起协程,保存句柄到等待列表 void await_suspend(std::coroutine_handle<> coro) noexcept { coro_ = coro; event_.waiters_.push_back(coro); } // 协程恢复时的操作,无返回值 void await_resume() noexcept {} }; co_await awaiter{*this}; } private: async::any_io_executor executor_; bool raised_ = false; std::vector<std::coroutine_handle<>> waiters_; // 存储等待的协程句柄 };
代码解释
线程安全保障:
- 构造函数接收
any_io_executor(比如io_context::get_executor()或strand包装的执行器),所有状态修改操作通过async::post提交到执行器上下文,避免多线程并发访问的竞态问题。 raise()会在执行器的线程中完成状态更新和协程恢复,确保操作原子性。
- 构造函数接收
Awaitable实现:
wait()返回的awaitable内部定义了符合C++20协程要求的awaiter结构体,实现三个核心方法:await_ready():提前检查事件是否已触发,若已触发则协程直接继续,无需挂起。await_suspend():将当前协程的句柄加入等待列表,完成挂起逻辑。await_resume():协程恢复时无额外操作,仅作为协程继续执行的入口。
多等待者支持:
- 通过
std::vector<std::coroutine_handle<>>存储所有等待的协程句柄,调用raise()时一次性恢复所有挂起的协程,解决了旧Boost.Coroutine类无法适配多协程等待的问题。
- 通过
Boost Asio与C++20协程的协作机制
- Boost Asio的
awaitable是对C++20无栈协程的封装,自动将协程调度与Asio的执行上下文(io_context/strand)绑定,确保协程在正确的线程中恢复。 - 使用
co_await挂起协程时,Asio会将协程句柄注册到事件源(这里是oneshot_event),待事件触发后再将协程重新加入执行队列完成恢复。 - 旧的Boost.Coroutine基于栈式协程,与C++20无栈协程机制完全不同,且旧
coro类仅针对单个协程生命周期控制,无法方便实现多协程等待同一事件的场景,也难以与Asio异步模型无缝集成。
使用示例
async::awaitable<void> demo(async::io_context& io_ctx) { oneshot_event event(io_ctx.get_executor()); // 启动两个协程等待事件 async::co_spawn(io_ctx, [&event]() -> async::awaitable<void> { co_await event.wait(); std::cout << "Coroutine 1 resumed\n"; }, async::detached); async::co_spawn(io_ctx, [&event]() -> async::awaitable<void> { co_await event.wait(); std::cout << "Coroutine 2 resumed\n"; }, async::detached); // 1秒后触发事件 co_await async::steady_timer(io_ctx, std::chrono::seconds(1)).async_wait(async::use_awaitable); event.raise(); } int main() { async::io_context io_ctx; async::co_spawn(io_ctx, demo(io_ctx), async::detached); io_ctx.run(); return 0; }
注意事项
- 若需要可重置事件,可添加
reset()方法,重置raised_为false并清空等待列表,但需确保操作在执行器上下文中完成,保证线程安全。 - 禁止在未绑定执行器的情况下使用
oneshot_event,否则会出现线程安全问题。 oneshot_event触发后,后续调用wait()会直接返回,不会挂起协程。
内容的提问来源于stack exchange,提问作者tuxifan
相关产品推荐
相关产品推荐

