C++每日训练 Day 13:协程驱动的信号机制与异步事件整合实战

📘 本篇围绕现代 C++ 协程(coroutine)能力,结合我们在前几日构建的信号机制(Signal/Slot),完整实现一个可以 co_await 的异步信号框架,支持非阻塞、生命周期安全、简洁自然的事件驱动流程。文章从基础概念入手,逐步展开协程、std::futurestd::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 到线程池执行
  • 引入线程调度器封装 + 事件优先级处理
  • 建立多事件队列的异步调度中心,支持跨模块触发
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值