ASIO协程中自定义可等待对象与协程恢复的实现问题
问题描述
我有一个代表ASIO连接的类(省略无关代码):
template<Enumeration T> class connection : public std::enable_shared_from_this<connection<T>> { ... // 线程安全队列 ts_queue<message<T>> m_qMessagesIn; std::coroutine_handle<> m_resumeCoro{}; ... public: ... asio::awaitable<void> listen() { auto self = this->shared_from_this(); for (;;) { auto ret = co_await (readMessage() || watchdog()); if (ret.index() == 1) co_return; ... auto& msgOpt = std::get<std::optional<message<T>>>(ret); if (not msgOpt.has_value()) co_return; auto& msg = msgOpt.value(); m_qMessagesIn.push_back(std::move(msg)); // ================================================== // 想要实现的逻辑 if (m_resumeCoro) m_resumeCoro.resume(); ... }
我希望在消息到达时恢复正在co_await消息的协程,于是在类中添加了一个函数:
auto message() { struct awaitable { connection* m_pConn; std::optional<message<T>> m_msg; awaitable(connection* pConn) : m_pConn{ pConn } {} bool await_ready() { return m_pConn->hasMessage(); } void await_suspend(std::coroutine_handle<> coro) { // 保存句柄以便后续恢复 m_pConn->m_resumeCoro = coro; } auto await_resume() { m_pConn->m_resumeCoro = nullptr; return std::move(m_pConn->m_qMessagesIn.front()); } }; return awaitable{ this }; } };
该函数本应挂起调用协程并保存句柄,直到消息到达后恢复协程,使用方式如下:
asio::awaitable<void> useConnection() { connection<T> conn = ...; ... auto msg = co_await conn.message(); // <-- 编译失败 // 错误:no matching member function for call to 'await_transform' }
但代码无法编译,推测与ASIO内部机制有关。已了解async_initiate,但不确定是否适用。请问实现该需求的正确方式是什么?
解决方案
编译错误的核心原因是:ASIO的awaitable协程要求被co_await的对象必须能被ASIO的await_transform处理。自定义的awaitable结构体没有适配ASIO的协程上下文,因此触发了该错误。
方案1:封装为asio::awaitable类型的函数
直接将等待逻辑封装成返回asio::awaitable<message<T>>的成员函数,利用ASIO原生的协程机制处理挂起与恢复:
asio::awaitable<message<T>> message() { auto self = shared_from_this(); // 如果队列已有消息,直接取出返回 if (hasMessage()) { auto msg = std::move(m_qMessagesIn.front()); m_qMessagesIn.pop_front(); co_return msg; } // 挂起协程,等待消息触发 auto promise = co_await asio::experimental::make_promise<void>(); self->m_promise = std::move(promise); // 消息到达后恢复,取出并返回消息 auto msg = std::move(m_qMessagesIn.front()); m_qMessagesIn.pop_front(); co_return msg; }
同时修改listen函数中的触发逻辑,替换原有的m_resumeCoro恢复代码:
// 消息入队后触发协程恢复 if (m_promise) { m_promise.set_value(); m_promise = nullptr; }
需要在connection类中添加成员变量:asio::experimental::promise<void> m_promise;。
方案2:用async_initiate封装异步操作
如果需要保留手动管理完成逻辑的方式,可通过async_initiate将等待逻辑适配为ASIO的异步操作模型:
template<typename CompletionToken> auto async_message(CompletionToken&& token) { return asio::async_initiate<CompletionToken, void(message<T>)>( [self = shared_from_this()](auto&& completion_handler) { // 队列有消息则立即完成 if (self->hasMessage()) { auto msg = std::move(self->m_qMessagesIn.front()); self->m_qMessagesIn.pop_front(); asio::dispatch( self->get_executor(), [handler = std::forward<decltype(completion_handler)>(completion_handler), msg = std::move(msg)]() mutable { handler(std::move(msg)); } ); return; } // 保存完成句柄,等待消息触发 self->m_completionHandler = std::forward<decltype(completion_handler)>(completion_handler); }, token ); }
修改listen函数中的触发逻辑:
if (m_completionHandler) { auto msg = std::move(m_qMessagesIn.front()); m_qMessagesIn.pop_front(); asio::dispatch( get_executor(), [handler = std::move(m_completionHandler), msg = std::move(msg)]() mutable { handler(std::move(msg)); } ); m_completionHandler = nullptr; }
使用时直接调用co_await conn.async_message();即可,ASIO会自动处理协程上下文的适配。
关键注意事项
- 线程安全:所有操作
m_promise、m_completionHandler或消息队列的代码必须保证线程安全,尤其是在多线程ASIO环境下。 - 生命周期管理:必须使用
shared_from_this()确保connection对象在异步操作完成前不会被销毁。 - 避免重复触发:每次触发后要及时清空保存的promise或完成句柄,防止重复调用导致未定义行为。
内容的提问来源于stack exchange,提问作者Matthias Grün
相关产品推荐
相关产品推荐

