You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 02:59:53