📘 本篇围绕现代 C++ 协程(coroutine)能力,结合我们在前几日构建的信号机制(Signal/Slot),完整实现一个可以
co_await
的异步信号框架,支持非阻塞、生命周期安全、简洁自然的事件驱动流程。文章从基础概念入手,逐步展开协程、std::future
、std::promise
、挂起/恢复机制与任务封装,并通过具体案例完整呈现协程式信号响应的落地实践。
🔁 Day 12 回顾:信号槽机制核心要点
技术维度 | 回顾重点 |
---|---|
Signal 模板 | 支持任意参数、可连接多个槽函数、统一调用入口 emit |
bindWeakMember | 支持成员函数 + 生命周期绑定,避免悬空调用 |
connectOnce | 实现一次性订阅,自动解绑 |
模块间通信 | 信号触发业务通知,模块解耦明确 |
📌 我们将以此为基础,引入协程挂起机制,实现:当事件未发生时暂停执行,事件到来时自动 resume。
🎯 今日目标:构建协程友好的信号等待与 resume 系统
目标模块 | 功能说明 |
---|---|
SignalAwaiter | 将信号转化为 awaitable 的封装器 |
Task | 协程任务结果容器,实现 promise_type |
AsyncSignal | 发射信号并唤醒等待协程 |
实战案例 | 实现异步登录信号等待与恢复流程 |
✅ 一、基础知识:什么是协程?为什么要用协程接信号?
📌 协程定义(coroutine):
协程是一个可以挂起(suspend)和恢复(resume)执行状态的函数。
📌 特点:
- 写法像同步流程,但本质异步
- 支持延迟 resume,挂起后不阻塞主线程
- 可以通过
co_await
等待外部事件(如网络、信号、UI)
📌 在信号场景中,我们希望写出如下自然结构:
std::string result = co_await signal.wait();
🧠 为什么要协程而不是回调?
回调问题 | 协程优势 |
---|---|
回调地狱(嵌套多层) | 写法线性,自然表达流程 |
生命周期难控(悬空指针) | 支持弱引用封装 resume |
难以跨模块组织状态 | 协程可封装上下文状态 |
✅ 二、构建 SignalAwaiter:让信号支持 co_await
🔸 Awaiter 核心结构:
template<typename T>
struct SignalAwaiter {
std::optional<T> result;
std::coroutine_handle<> handle;
bool await_ready() const noexcept { return false; } // 必须挂起
void await_suspend(std::coroutine_handle<> h) { handle = h; }
T await_resume() { return *result; }
void resume(T value) {
result = std::move(value);
handle.resume();
}
};
📌 用途说明:
- 保存协程挂起点
handle
- 当
signal.emit(x)
触发时,调用resume(x)
,恢复执行
✅ 三、自定义协程 Task 类型(简化 future)
template<typename T = void>
struct Task {
struct promise_type {
T value;
Task get_return_object() {
return Task{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(T v) { value = v; }
void unhandled_exception() {}
};
std::coroutine_handle<promise_type> handle;
T result() { return handle.promise().value; }
};
📌 对比 std::future:
- 更轻量
- 适合小型事件驱动协程
- 可自定义 suspend 行为(以后可接线程调度器)
✅ 四、设计协程信号类 AsyncSignal
template<typename T>
class AsyncSignal {
public:
void emit(const T& value) {
for (auto* waiter : waiters) {
waiter->resume(value);
}
waiters.clear();
}
SignalAwaiter<T>* wait() {
auto* waiter = new SignalAwaiter<T>();
waiters.push_back(waiter);
return waiter;
}
private:
std::vector<SignalAwaiter<T>*> waiters;
};
📌 功能总结:
- 每次 wait() 返回一个 awaitable 对象
- emit 时恢复所有等待的协程
- 支持多个协程同时等待同一事件
✅ 五、实战案例:异步登录 + 协程等待
🧩 LoginService:发射信号类
class LoginService {
public:
AsyncSignal<std::string> loginSignal;
void simulateLogin() {
std::this_thread::sleep_for(std::chrono::seconds(1));
loginSignal.emit("user_abc");
}
};
🧩 协程处理者:等待登录结果
Task<void> waitForLogin(LoginService& s) {
std::string user = co_await *s.loginSignal.wait();
std::cout << "✅ 登录成功,欢迎:" << user << std::endl;
co_return;
}
🧪 主函数入口
int main() {
LoginService login;
auto task = waitForLogin(login);
std::thread t([&]() {
login.simulateLogin();
});
t.join();
return 0;
}
📌 输出:
✅ 登录成功,欢迎:user_abc
📌 协程优点:代码结构清晰,不需要设置回调函数、状态机或 lambda 嵌套。
✅ 六、实战总结与设计建议
功能目标 | 实现方式 |
---|---|
非阻塞信号等待 | 协程 await 信号封装器 SignalAwaiter |
信号触发 resume 协程 | handle.resume() 恢复执行 |
多协程监听 | 每次 wait() 返回独立对象 |
参数类型泛化支持 | 使用模板封装 SignalAwaiter 与 Task |
📌 项目建议:
- 可与事件总线结合,支持 eventName + 任意 T 泛型事件传递
- 可接线程池,实现事件 resume 分发
- 后续可集成 GUI 主线程调度器,实现 GUI 响应式操作系统模型
🔭 Day 14 预告:线程调度下的异步信号系统设计
我们将在下一篇探索:
- 支持
co_await
信号 resume 到线程池执行 - 引入线程调度器封装 + 事件优先级处理
- 建立多事件队列的异步调度中心,支持跨模块触发