如何在Boost.Asio协程中自定义协程状态?
实现Boost.Asio协程的自定义线程局部状态
一、为什么不能直接扩展yield_context?
Boost.Asio的yield_context本质是轻量级控制适配器,设计目标仅传递协程调度必需的核心信息(cancellation_slot、executor),并不承载用户自定义状态。从源码逻辑看,yield_context内部持有对ASIO私有coroutine_context的引用,这个内部结构负责协程栈管理与调度,对外完全不透明——用户无法直接修改或扩展它的成员。强行继承或包装yield_context会破坏ASIO内部的协程调度逻辑,因为绝大多数异步操作的重载都是针对原生yield_context设计的。
二、可行方案1:线程局部存储+自定义协程启动包装
核心思路
ASIO协程虽无全局可见的唯一标识,但可通过线程局部指针绑定协程状态,在协程启动时创建状态实例,执行时让当前协程直接访问,退出时自动清理。
具体实现
1. 定义自定义状态类
struct CoroutineState { std::vector<std::string> log_scopes; // 日志命名作用域列表 // 可按需添加其他状态字段 };
2. 全局容器与线程局部指针
#include <unordered_map> #include <memory> #include <thread> #include <mutex> #include <boost/asio.hpp> // 全局容器存储所有协程状态,用状态实例地址作为唯一键 std::unordered_map<const void*, std::unique_ptr<CoroutineState>> g_coroutine_states; std::mutex g_states_mutex; // 线程安全锁 // 线程局部指针,指向当前执行协程的状态 thread_local CoroutineState* t_current_state = nullptr;
3. 自定义协程启动包装
替代原生boost::asio::spawn,自动为新协程绑定状态:
template <typename Executor, typename Coroutine> void spawn_with_state(const Executor& ex, Coroutine&& coro) { auto state = std::make_unique<CoroutineState>(); auto state_ptr = state.get(); // 存入全局容器(线程安全) { std::lock_guard<std::mutex> lock(g_states_mutex); g_coroutine_states.emplace(state_ptr, std::move(state)); } boost::asio::spawn(ex, [state_ptr, coro = std::forward<Coroutine>(coro)](boost::asio::yield_context yield) mutable { // 保存旧状态,切换为当前协程状态 CoroutineState* old_state = t_current_state; t_current_state = state_ptr; try { // 执行用户协程逻辑 coro(yield); } catch (...) { // 异常时恢复旧状态 t_current_state = old_state; throw; } // 协程退出,恢复旧状态并清理全局容器 t_current_state = old_state; std::lock_guard<std::mutex> lock(g_states_mutex); g_coroutine_states.erase(state_ptr); }); }
4. 日志场景示例
void log_with_scope(const std::string& msg) { if (t_current_state) { std::string scope_prefix; for (const auto& scope : t_current_state->log_scopes) { scope_prefix += "[" + scope + "]"; } std::cout << scope_prefix << " " << msg << std::endl; } else { std::cout << msg << std::endl; } } void my_coroutine(boost::asio::yield_context yield) { t_current_state->log_scopes.push_back("my_coroutine"); log_with_scope("Start working"); // 嵌套启动协程 spawn_with_state(yield.get_executor(), [](boost::asio::yield_context yield) { t_current_state->log_scopes.push_back("nested_coroutine"); log_with_scope("Nested coroutine running"); }); log_with_scope("Finish working"); t_current_state->log_scopes.pop_back(); }
三、更优雅的方案2:利用cancellation_slot关联状态
每个协程的cancellation_slot是唯一的,且其生命周期与协程绑定,可直接用它关联自定义状态,无需自定义spawn包装:
实现代码
#include <unordered_map> #include <memory> #include <mutex> #include <boost/asio.hpp> struct CoroutineState { std::vector<std::string> log_scopes; }; // 获取当前协程的状态 CoroutineState& get_current_coroutine_state(boost::asio::yield_context yield) { static std::unordered_map<const boost::asio::cancellation_slot*, std::unique_ptr<CoroutineState>> state_map; static std::mutex map_mutex; auto& slot = yield.get_cancellation_slot(); std::lock_guard<std::mutex> lock(map_mutex); auto it = state_map.find(&slot); if (it == state_map.end()) { auto state = std::make_unique<CoroutineState>(); it = state_map.emplace(&slot, std::move(state)).first; // 协程结束时(slot销毁)自动清理状态 slot.assign([&slot, &state_map, &map_mutex]() { std::lock_guard<std::mutex> lock(map_mutex); state_map.erase(&slot); }); } return *it->second; } // 使用示例 void my_coroutine(boost::asio::yield_context yield) { auto& state = get_current_coroutine_state(yield); state.log_scopes.push_back("my_coroutine"); // 日志逻辑... }
四、关键注意事项
- 线程安全:全局容器必须加锁,避免多线程调度协程时的竞态问题。
- 嵌套协程:线程局部指针方案需保存并恢复旧状态,防止嵌套协程的状态互相覆盖。
- 内存泄漏:利用
cancellation_slot的回调或lambda生命周期自动清理状态,避免内存泄漏。
内容的提问来源于stack exchange,提问作者unegare
相关产品推荐
相关产品推荐

