📘 本篇目标是:在理解协程信号机制基础上,加入线程调度能力,实现一个支持异步 resume 到特定线程(如主线程或线程池)的 Signal/Slot 系统。内容尽量降低门槛,所有示例都附详细注释,配合常见问题解析帮助你一步步构建清晰的异步事件处理系统。
🔁 Day 13 回顾:协程驱动的信号机制
内容点 | 说明 |
---|---|
SignalAwaiter | 信号挂起点,保存 coroutine_handle,emit 后 resume 执行 |
Task 协程封装 | 提供简单 promise_type,支持 return_value 与 result 获取 |
AsyncSignal | 支持 emit 触发协程 resume,多个协程同时等待 |
📌 本篇将在结构中加入线程调度能力,让 emit 可以 resume 协程到 指定线程或线程池执行。
🎯 今日目标:让信号 resume 协程时运行在指定线程中
功能模块 | 作用说明 |
---|---|
调度器接口 Dispatcher | 接口类,定义 dispatch 到指定线程的能力 |
ThreadDispatcher | 实现简单线程队列调度 |
AwaiterWithDispatcher | resume 协程时先交给调度器派发执行 |
应用案例 | 在信号 emit 后 resume 到主线程 |
✅ 一、构建线程调度器接口
class Dispatcher {
public:
virtual void dispatch(std::function<void()> task) = 0;
virtual ~Dispatcher() = default;
};
// 📌 这是一个调度器接口,任何线程执行者都可以实现它。
✅ 二、构建主线程/单线程任务队列
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
class ThreadDispatcher : public Dispatcher {
public:
ThreadDispatcher() : running(true), worker([this](){ this->run(); }) {}
void dispatch(std::function<void()> task) override {
{
std::lock_guard<std::mutex> lock(mtx);
tasks.push(std::move(task));
}
cv.notify_one();
}
void stop() {
running = false;
cv.notify_all();
worker.join();
}
private:
std::queue<std::function<void()>> tasks;
std::mutex mtx;
std::condition_variable cv;
bool running;
std::thread worker;
void run() {
while (running) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !tasks.empty() || !running; });
if (!running && tasks.empty()) break;
task = std::move(tasks.front());
tasks.pop();
}
task(); // ✅ 执行任务
}
}
};
// 📌 使用 dispatch() 可向此线程提交任务。
✅ 三、构建 AwaiterWithDispatcher
#include <optional>
#include <coroutine>
template<typename T>
struct AwaiterWithDispatcher {
std::optional<T> result;
std::coroutine_handle<> handle;
Dispatcher* dispatcher;
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);
dispatcher->dispatch([handle = handle]() { handle.resume(); }); // 📌 在调度器上 resume
}
};
// 📌 使用调度器 resume 协程,resume 的动作将转移到 dispatcher 所在线程。
✅ 四、封装 AsyncSignalWithDispatcher
template<typename T>
class AsyncSignalWithDispatcher {
public:
explicit AsyncSignalWithDispatcher(Dispatcher* d) : dispatcher(d) {}
void emit(const T& value) {
for (auto* waiter : waiters) {
waiter->resume(value);
}
waiters.clear();
}
AwaiterWithDispatcher<T>* wait() {
auto* w = new AwaiterWithDispatcher<T>();
w->dispatcher = dispatcher;
waiters.push_back(w);
return w;
}
private:
Dispatcher* dispatcher;
std::vector<AwaiterWithDispatcher<T>*> waiters;
};
// 📌 信号将 resume 协程到 dispatcher 所在线程执行。
✅ 五、完整实战:主线程 resume 协程等待信号
Task<void> waitInMainThread(AsyncSignalWithDispatcher<std::string>& sig) {
std::string v = co_await *sig.wait();
std::cout << "✅ 协程 resume 于主线程,值是:" << v << std::endl;
co_return;
}
主函数:
int main() {
ThreadDispatcher mainThreadDispatcher;
AsyncSignalWithDispatcher<std::string> sig(&mainThreadDispatcher);
auto task = waitInMainThread(sig);
std::thread t([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
sig.emit("signal_from_worker");
});
std::this_thread::sleep_for(std::chrono::seconds(2));
mainThreadDispatcher.stop();
t.join();
return 0;
}
📌 说明:
- emit 在 worker 线程调用
- 协程 resume 到 mainThreadDispatcher 所在线程(即主线程)
输出示例:
✅ 协程 resume 于主线程,值是:signal_from_worker
✅ 六、总结与通俗理解
功能 | 你应该记住的关键点 |
---|---|
resume 到主线程 | 用 dispatch 包住 resume 调用 |
使用线程调度器 | 提交任务到你想要 resume 的线程 |
协程挂起点安全封装 | 用 awaiter 保存 coroutine_handle,再 resume |
emit 不等于执行 | emit 只负责通知 + 派发,真正 resume 是由 dispatcher 决定 |
📌 小结:事件发生 ≠ 马上执行代码,事件发生 + 指定线程 resume 才是现代异步架构!
📘 学习巩固:问题与答案
Q1:dispatcher 是做什么的?
A:dispatcher 是任务调度器,负责将 resume 协程的任务交给特定线程处理。
Q2:为什么 emit 不直接 resume?
A:直接 resume 会在当前线程运行,但我们常需要 resume 到主线程/UI线程或线程池。
Q3:协程 await 时在做什么?
A:await 会暂停协程执行,把控制权交给 dispatcher,直到 signal emit 再 resume。
Q4:多个协程等待 signal 会怎么样?
A:所有 waiter 都会被触发,各自 resume 到各自 dispatcher。
Q5:是否可以结合线程池?
A:完全可以,只要线程池实现了 Dispatcher 接口,就可以作为调度器。
🔭 下一站 Day 15 预告:结合线程池、事件队列,构建高性能信号调度中心
我们将实现:
- 多线程下统一信号中心
- 线程池处理 resume 协程
- 带事件 ID 的泛型调度器 + 动态订阅系统
📌 如果你有特定实战需求(如跨线程 UI 更新、网络 IO 回调合流等),欢迎留言告诉我 ✅