协程封装redis异步驱动的核心是桥接hiredis与asio事件循环,将socket可读/可写等待协程化;需绑定唯一stream_descriptor、设置连接/断连回调、深拷贝reply、校验fd有效性并快照错误状态。

协程封装 Redis 异步驱动的核心约束
不能直接把 hiredis 的 redisAsyncConnect 塞进协程里就完事——它本身不挂起,也不返回 std::suspend_always。真正能协程化的,是「等待事件循环通知连接就绪/命令回复到达」这个动作。所以必须桥接异步 I/O 库(如 libuv 或 boost::asio)和协程调度器,让 co_await 等在 socket 可读/可写上,而不是等线程 sleep。
用 boost::asio + redisAsyncContext 绑定协程等待点
hiredis 的异步 API 依赖用户自己提供事件循环回调;boost::asio 的 io_context 正好能接管这个职责。关键不是重写 hiredis,而是把它的 socket fd 注册进 asio::posix::stream_descriptor,再用 async_wait 或 async_read_some 封装等待逻辑。
- 必须调用
redisAsyncSetConnectCallback和redisAsyncSetDisconnectCallback,否则连接成功后协程收不到通知 - 每个
redisAsyncContext*要绑定唯一一个asio::posix::stream_descriptor,重复绑定会触发Bad file descriptor - 不要在协程里直接调用
redisAsyncCommand后立刻co_await——它只是发请求,不保证发送完成;得等AE_WRITABLE事件确认 socket 可写且缓冲区有空位 - 示例等待连接建立:
co_await async_wait_descriptor(desc, asio::posix::stream_descriptor::wait_write);
实际中还要检查context->err是否为 0
协程返回值与错误传播的常见翻车点
很多人想让 co_await redis_get("key") 直接返回 std::string,但 hiredis 的 reply 是 redisReply*,生命周期由回调函数管理,协程挂起时 reply 可能已被释放。
- 必须在回调里深拷贝
redisReply字段(如str、len),或用redisGetReply配合同步上下文——但这会阻塞线程,违背初衷 - 推荐用
std::optional<:string></:string>作返回类型,std::nullopt表示网络错误或空值,避免裸指针逃逸 - 如果用了
redisAsyncCommandArgv,参数argv数组必须在协程整个生命周期内有效——建议用std::vector<:string></:string>持有,传argv.data()时确保 vector 不 realloc - 超时不能只靠协程内部计时器:
hiredis的redisAsyncSetTimeout才真正控制 socket 层超时,协程侧的std::chrono::steady_clock只能做兜底
连接池与协程调度器的生命周期对齐
协程可能跨多个 io_context.run() 调用,而 redisAsyncContext 关联的 socket fd 在 disconnect 后失效。若连接池复用 context 但没重置 fd,下次 co_await 会卡死或触发 Bad file descriptor。
立即学习“C++免费学习笔记(深入)”;
- 每次从连接池取 context 前,必须检查
context->c.fd != -1 && context->err == 0,否则重建连接 - 不要把
io_context对象放在栈上并传给协程——协程可能被挂起,io_context析构后所有 pending await 都会崩溃 - 若用
boost::asio::thread_pool,确保所有 Redis 协程都在同一个io_context上调度,混用不同上下文会导致事件丢失 - 连接池 size 不宜过大:每个
redisAsyncContext占一个 socket fd + 回调注册开销,Linux 默认单进程 1024 fd 限制下,20 个连接就占满一半
最麻烦的其实是错误上下文丢失:hiredis 把错误码存在 context->err 里,但协程恢复时这个值可能已被后续命令覆盖。真要可靠,得在每次回调里立刻把 err 和 errstr 快照进 promise 的局部状态。










