在昨天的文章《C++实现简化版Qt的QObject(5):通过IEventLoopHost扩展实现win32消息循环》里,我们对EventLoop增加了Win32消息循环的支持。
并支持post异步和延时任务。
但是,还不支持timer的多次触发的定时器。今天我们一起来增加这个功能。
接口设计
计划我们的Timer在CEventLoop中新增以下接口
// 启动一个周期性的定时器
CancelHandler startTimer(Handler handler, Duration interval)
// 停止一个周期性的定时器
void stopTimer(CancelHandler& active) {
新增Timer机制
首先,我们希望对源代码修改尽量少的情况下增加定时器的能力。
于是,我决定在执行完task之后判断其是否是timer,如果是则再添加回task_队列中去,核心代码如下:
因此,task的结构体中,需要增加bool repeat字段。
取消定时器
考虑定时器的取消逻辑。一般来说设置完定时器之后会返回一个timerID之类的标识符,用于在stopTimer的时候制定具体的Timer。但是我们的任务队列task_是个std::priority_queue,只支持pop,并且遍历搜索的性能也较低,综合考虑后,我决定返回一个shared_ptr,这个指针指向一个bool标识符,标识Timer是否生效,只需要修改这个指针指向的值,就可以完成取消操作。
shared_ptr被定义为:using CancelHandle = std::shared_ptr<bool>;
增加了两个字段后,TaskEventInfo 完整的结构体定义如下:
struct TaskEventInfo {
TimePoint time;
Handler handler;
Duration interval;
bool repeat = false;
std::shared_ptr<bool> active;
bool operator<(const TaskEventInfo& other) const {
return time > other.time;
}
};
新增逻辑:在执行任务前,需要先验证timer是否有效:
那么,我们的stopTimer
函数就很简单了:
// 停止一个周期性的定时器
void stopTimer(CancelHandle& active) {
if (active) { *active = false; }
}
使用示例
CWindowsEventLoopHost host;
base::CEventLoop loop(&host);
auto timerId = loop.startTimer([] {
std::cout << "Periodic timer 500ms triggered." << std::endl;
}, std::chrono::milliseconds(500));
loop.post([&]() {
std::cout << "timer stoped!\n";
loop.stopTimer(timerId);
}, std::chrono::seconds(4));//延时4秒
扩展post功能
既然timer可以cancel,为什么post的任务不可以?因此我们新增一个函数,叫postCancellable
,这个函数会在post的时候返回一个CancelHandle
,这样就可以取消post任务了:
//可以取消的post
CancelHandle postCancellable(Handler handler, Duration delay = Duration::zero()) {
std::unique_lock<std::mutex> lock(mutex_);
TaskEventInfo timedHandler{ Clock::now() + delay, std::move(handler), {}, false, std::make_shared<bool>(true) };
tasks_.push(timedHandler);
cond_.notify_one();
if (host) {
host->onPostTask();
}
return timedHandler.active;
}
// 停止一个周期性的定时器
void cancelPostTask(CancelHandle& active) {
if (active) { *active = false; }
}
post函数依然返回空。之所以要新增postCancellable
而非直接修改post
,是因为大部分场景的post是不需要cancel的,而且如果要支持cancel需要额外new 一个shared_ptr,如果不需要cancel那就可以省下这个性能损耗了。
提交代码
今天增加完这些功能,并优化以下命名,提交到git上。
https://github.com/kevinyangli/simple_qt_qobject.git
今天的代码改动如下:
src/simple_qobject.h | 81 +++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 71 insertions(+), 10 deletions(-)
diff --git a/src/simple_qobject.h b/src/simple_qobject.h
index 35c0d03..4d2995e 100644
--- a/src/simple_qobject.h
+++ b/src/simple_qobject.h
@@ -199,7 +199,7 @@ namespace refl {
};
// 上面都是静态反射功能,以下是动态反射机制的支持代码:
-
+
// IReflectable提供动态反射功能的支持
class IReflectable : public std::enable_shared_from_this<IReflectable> {
public:
@@ -482,10 +482,15 @@ namespace base {
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
using Handler = std::function<void()>;
- struct TimedHandler {
+ using CancelHandle = std::shared_ptr<bool>;
+
+ struct TaskEventInfo {
TimePoint time;
Handler handler;
- bool operator<(const TimedHandler& other) const {
+ Duration interval;
+ bool repeat = false;
+ std::shared_ptr<bool> active;
+ bool operator<(const TaskEventInfo& other) const {
return time > other.time;
}
};
@@ -495,21 +500,35 @@ namespace base {
virtual ~IEventLoopHost() = default;
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 onEvent(TaskEventInfo& 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::priority_queue<TaskEventInfo> tasks_;
std::mutex mutex_;
std::condition_variable cond_;
std::atomic<bool> running_{ true };
+ static thread_local CEventLoop* s_currentThreadEventLoop;
public:
CEventLoop(IEventLoopHost* host = nullptr) {
this->host = host;
host->eventLoop = this;
+ if (s_currentThreadEventLoop == nullptr) {
+ s_currentThreadEventLoop = this;
+ }
+ }
+ ~CEventLoop() {
+ if (s_currentThreadEventLoop == this) {
+ s_currentThreadEventLoop = nullptr;
+ }
+ }
+
+ CEventLoop* currentThreadEventLoop() {
+ return s_currentThreadEventLoop;
}
+
void post(Handler handler, Duration delay = Duration::zero()) {
std::unique_lock<std::mutex> lock(mutex_);
tasks_.push({ Clock::now() + delay, std::move(handler) });
@@ -519,6 +538,37 @@ namespace base {
}
}
+ //可以取消的post
+ CancelHandle postCancellable(Handler handler, Duration delay = Duration::zero()) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ TaskEventInfo timedHandler{ Clock::now() + delay, std::move(handler), {}, false, std::make_shared<bool>(true) };
+ tasks_.push(timedHandler);
+ cond_.notify_one();
+ if (host) {
+ host->onPostTask();
+ }
+ return timedHandler.active;
+ }
+
+ // 停止一个周期性的定时器
+ void cancelPostTask(CancelHandle& active) {
+ if (active) { *active = false; }
+ }
+
+ // 启动一个周期性的定时器
+ CancelHandle startTimer(Handler handler, Duration interval) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ TaskEventInfo timedHandler{ Clock::now() + interval, std::move(handler), interval, true, std::make_shared<bool>(true) };
+ tasks_.push(timedHandler);
+ cond_.notify_one();
+ return timedHandler.active;
+ }
+
+ // 停止一个周期性的定时器
+ void stopTimer(CancelHandle& active) {
+ if (active) { *active = false; }
+ }
+
void run() {
while (running_) {
std::unique_lock<std::mutex> lock(mutex_);
@@ -537,13 +587,24 @@ namespace base {
auto task = tasks_.top();
tasks_.pop();
lock.unlock();
- if (host) {
- host->onEvent(task);
+ bool isActive = task.active.get() ? (*task.active.get()) : true;
+ if (isActive) {
+ if (host) {
+ host->onEvent(task);
+ }
+ else {
+ task.handler();
+ }
+ lock.lock();
+
+ if (task.repeat) { // 是个timer,计算下一次触发时机并放回去
+ task.time = Clock::now() + task.interval;
+ tasks_.push(task);
+ }
}
else {
- task.handler();
+ lock.lock();
}
- lock.lock();
}
if (!tasks_.empty()) {
@@ -596,7 +657,7 @@ public:
}
}
- void onEvent(base::CEventLoop::TimedHandler& event) override {
+ void onEvent(base::CEventLoop::TaskEventInfo& event) override {
event.handler();
}