在上一篇文章《C++实现简化版Qt的QObject(4):增加简单实用的事件机制》中,我们实现了普通线程的事件机制。
但是事件机制往往需要和操作系统主线程消息循环一起工作。
因此,今天,我们在之前的CEventLoop的实现之上,通过IEventLoopHost扩展,实现一个windows系统的主线程消息循环扩展,使得我们可以在主线程post task。
CEventLoop 代码
首先,昨天我们写的CEventLoop代码如下:
cpp
class CEventLoop {
public:
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
using Handler = std::function<void()>;
struct TimedHandler {
TimePoint time;
Handler handler;
bool operator<(const TimedHandler& other) const {
return time > other.time;
}
};
class IEventLoopHost {
public:
virtual void onPostTask() = 0;
virtual void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) = 0;
virtual void onEvent(TimedHandler& event) = 0;
virtual void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const TimePoint& timePoint) = 0;
};
private:
IEventLoopHost* host = nullptr;
std::priority_queue<TimedHandler> tasks_;
std::mutex mutex_;
std::condition_variable cond_;
std::atomic<bool> running_{ true };
public:
void setHost(IEventLoopHost* host) {
this->host = host;
}
void post(Handler handler, Duration delay = Duration::zero()) {
std::unique_lock<std::mutex> lock(mutex_);
tasks_.push({ Clock::now() + delay, std::move(handler) });
cond_.notify_one();
if (host) {
host->onPostTask();
}
}
void run() {
while (running_) {
std::unique_lock<std::mutex> lock(mutex_);
if (tasks_.empty()) {
if (host) {
host->onWaitForTask(cond_, lock);
}
else {
cond_.wait(lock, [this] { return !tasks_.empty() || !running_; });
}
}
while (!tasks_.empty() && tasks_.top().time <= Clock::now()) {
auto task = tasks_.top();
tasks_.pop();
lock.unlock();
if (host) {
host->onEvent(task);
}
else {
task.handler();
}
lock.lock();
}
if (!tasks_.empty()) {
if (host) {
host->onWaitForRun(cond_, lock, tasks_.top().time);
}
else {
cond_.wait_until(lock, tasks_.top().time);
}
}
}
}
void stop() {
running_ = false;
cond_.notify_all();
}
};
实现扩展
在Windows API中,消息循环通常涉及到调用GetMessage
、TranslateMessage
和DispatchMessage
函数。以下是一个IEventLoopHost
的实现,它将Windows消息循环集成到CEventLoop
类中:
cpp
#include <Windows.h>
class WindowsEventLoopHost : public CEventLoop::IEventLoopHost {
public:
// 当任务被投递到事件循环时调用
void onPostTask() override {
// 可以使用Windows的PostMessage函数发送一个自定义的消息来唤醒消息循环
PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0);
}
// 当事件循环需要等待任务时调用
void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) override {
locker.unlock();
MSG msg;
// 使用PeekMessage而不是GetMessage来避免阻塞,以便定时器事件可以被处理
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
locker.lock();
}
// 当事件循环处理事件时调用
void onEvent(CEventLoop::TimedHandler& event) override {
// 这里简单地调用事件处理器
event.handler();
}
// 当事件循环需要等待直到特定时间点运行时调用
void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const CEventLoop::TimePoint& timePoint) override {
locker.unlock();
// 计算需要等待的时间
auto now = CEventLoop::Clock::now();
auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint - now).count();
if (delay_ms > 0) {
// 设置Windows计时器
SetTimer(NULL, 0, (UINT)delay_ms, NULL);
MSG msg;
// 等待计时器或其他消息
while (GetMessage(&msg, NULL, 0, 0)) {
if (msg.message == WM_TIMER) {
break; // 计时器消息,继续事件循环
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
KillTimer(NULL, 0);
}
locker.lock();
}
};
在这个实现中,我们通过IEventLoopHost
的四个虚函数,完成与Windows消息循环协同工作:
onPostTask
使用PostThreadMessage
来发送一个自定义的消息,以唤醒可能正在等待的消息循环。onWaitForTask
使用PeekMessage
来处理消息而不阻塞,这样定时器消息可以在适当的时间被处理。onEvent
简单地调用传入的事件处理器,这里我们没有对Windows消息做特别的处理。onWaitForRun
使用SetTimer
来创建一个Windows计时器,它会在指定的毫秒数后发送一个WM_TIMER
消息。我们使用GetMessage
等待这个消息或者其他消息的到来。如果计时器消息到来,我们就终止循环,返回到事件循环,以便继续处理其他任务。
继续优化
在自测过程中进一步的优化代码结构,优化性能,处理WM_QUIT消息,处理一些其他细节等。
最终代码如下:
cpp
#include <Windows.h>
class CWindowsEventLoopHost : public base::CEventLoop::IEventLoopHost {
static constexpr UINT WM_WAKEUP = WM_USER + 15151515;
UINT_PTR timerID_ = 0;
public:
~CWindowsEventLoopHost() {
if (timerID_) {
KillTimer(NULL, timerID_);
}
}
void onPostTask() override {
PostThreadMessage(GetCurrentThreadId(), WM_WAKEUP, 0, 0);
}
void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) override {
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
if (handleWindowsMessage(msg)) {
break;
}
if (cond.wait_for(locker, std::chrono::milliseconds(0), [] { return true; })) {
break;
}
}
}
void onEvent(base::CEventLoop::TimedHandler& event) override {
event.handler();
}
void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const std::chrono::steady_clock::time_point& timePoint) override {
auto now = std::chrono::steady_clock::now();
if (now < timePoint) {
auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint - now).count();
timerID_ = SetTimer(NULL, 0, (UINT)delay_ms, NULL);//通过timer事件唤醒
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
if (handleWindowsMessage(msg)) {
break;
}
}
}
}
bool handleWindowsMessage(MSG& msg) {
if (msg.message == WM_QUIT) {
if (this->eventLoop) {
this->eventLoop->stop();//退出消息循环
}
return true;
}
if (msg.message == WM_TIMER && msg.wParam == timerID_) {
KillTimer(NULL, timerID_);
timerID_ = 0;
return true; // Timer event occurred
}
TranslateMessage(&msg);
DispatchMessage(&msg);
return false;
}
};
使用示例
由于setHost函数里面没有加锁,而且可能出现在运行过程中被修改host出现不可预期情况。因此,后来去掉setHost函数,改为在CEventLoop的构造函数传入host,避免误用。
使用示例如下:
cpp
// 使用示例
int main() {
CWindowsEventLoopHost host;
base::CEventLoop loop(&host);
loop.post([]() {
std::cout << "Immediate task\n";
});//马上执行
loop.post([]() {
std::cout << "Delayed task\n";
}, std::chrono::seconds(1));//延时一秒
loop.post([]() {
std::cout << "Delayed task in 3 second\n";
}, std::chrono::seconds(3));//延时3秒
loop.post([&]() {
std::cout << "stop msg loop in 6 second\n";
loop.stop();
}, std::chrono::seconds(6));//延时6秒
if (true) {
loop.run();//主线程直接run
}
else {
// 或者起一个其他线程run:
std::thread loop_thread([&loop]() {
loop.run();
});
std::this_thread::sleep_for(std::chrono::seconds(10));
loop.stop();//停止消息循环
loop_thread.join();
}
}