

Qt源码阅读(四) 事件循环 - 师从名剑山
source link: https://www.cnblogs.com/codegb/p/17274163.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

事件系统
文章为本人理解,如有理解不到位之处,烦请各位指正。
Qt的事件循环,应该是所有Qter都避不开的一个点,所以,这篇博客,咱们来了解源码中一些关于Qt中事件循环的部分。
先抛出几个疑问,根据源代码,下面一一进行解析。
什么是事件循环?
对于Qt事件循环个人理解是,事件循环是一个队列去循环处理事件。当队列中有事件时,则去处理事件,如果没有事件时,则会阻塞等待。
事件是如何产生的?
事件的产生可以分为两种:
- 程序外部产生
- 程序内部产生
程序外部所产生的事件主要是指系统产生的事件,比如说鼠标按下(MouseButtonPress)、按键按下(KeyPress)等,Qt捕捉系统的事件,然后将系统事件封装成自己的QEvent
类,再将事件发送出去。
程序内部产生的事件主要指我们在代码里,手动创建一个事件,然后将事件通过sendEvent
/postEvent
,来发送到事件循环中。而sendEvent
和postEvent
区别又在于一个是阻塞的(sendEvent
)一个是非阻塞的(postEvent
)。
我们结合源码分析,看一下sendEvent
和postEvent
分别干了什么导致一个是阻塞的一个是非阻塞的。
1|0sendEvent
完整源码如下:
bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event) { // sendEvent是阻塞调用 Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());
if (event) event->spont = false; return notifyInternal2(receiver, event); }
可以看到,sendEvent
是调用了notifyInternal2
这个函数
进一步跟踪到其doNotify
函数
static bool doNotify(QObject *receiver, QEvent *event) { if (receiver == nullptr) { // serious error qWarning("QCoreApplication::notify: Unexpected null receiver"); return true; }
#ifndef QT_NO_DEBUG // 检查接受线程与当前是否同线程 QCoreApplicationPrivate::checkReceiverThread(receiver); #endif
// QWidget类必须用QApplication return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event); }
再到QCoreApplicationPrivate::notify_helper
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event) { // Note: when adjusting the tracepoints in here // consider adjusting QApplicationPrivate::notify_helper too. Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type()); bool consumed = false; bool filtered = false; Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);
// send to all application event filters (only does anything in the main thread) if (QCoreApplication::self && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread() && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) { filtered = true; return filtered; } // send to all receiver event filters if (sendThroughObjectEventFilters(receiver, event)) { filtered = true; return filtered; }
// deliver the event // 直接调用对象的event函数,所以是阻塞的 consumed = receiver->event(event); return consumed; }
然后我们可以看到主要有几个流程:
-
判断QCoreApplication有没有安装事件过滤器,有就把信号发送到事件过滤器里,由事件过滤器对事件进行处理。
// send to all application event filters (only does anything in the main thread) if (QCoreApplication::self && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread() && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) { filtered = true; return filtered; } -
判断事件接受对象,有没有安装事件过滤器,有就将信号发送到事件过滤器。
// send to all receiver event filters if (sendThroughObjectEventFilters(receiver, event)) { filtered = true; return filtered; }具体遍历事件接受对象所安装的事件过滤器的代码如下:
bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event) { if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) { for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) { QObject *obj = receiver->d_func()->extraData->eventFilters.at(i); if (!obj) continue; if (obj->d_func()->threadData != receiver->d_func()->threadData) { qWarning("QCoreApplication: Object event filter cannot be in a different thread."); continue; } if (obj->eventFilter(receiver, event)) return true; } } return false; }我们可以看到,只要事件被一个事件过滤器所成功处理,那么后续的事件过滤器就不会被响应。同时,参看Qt帮助手册中有提及到:
If multiple event filters are installed on a single object, the filter that was installed last is activated first.
后插入的事件过滤器会被优先响应。 具体安装事件过滤器,我们在后面进行分析。
-
直接调用事件接受对象的
event
函数进行处理。因为是直接调用的对象的event
,所以说,sendEvent
函数会阻塞等待。// deliver the event // 直接调用对象的event函数,所以是阻塞的 consumed = receiver->event(event); return consumed
1|0postEvent
完整代码如下:
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority) { Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());
// 事件的接收者不能为空 if (receiver == nullptr) { qWarning("QCoreApplication::postEvent: Unexpected null receiver"); delete event; return; }
// 对事件接受对象所在线程的事件处理列表上锁 auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver); if (!locker.threadData) { // posting during destruction? just delete the event to prevent a leak delete event; return; }
QThreadData *data = locker.threadData;
// if this is one of the compressible events, do compression // 将重复的事件,进行压缩 if (receiver->d_func()->postedEvents && self && self->compressEvent(event, receiver, &data->postEventList)) { Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event); return; }
if (event->type() == QEvent::DeferredDelete) receiver->d_ptr->deleteLaterCalled = true;
if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) { // remember the current running eventloop for DeferredDelete // events posted in the receiver's thread.
// Events sent by non-Qt event handlers (such as glib) may not // have the scopeLevel set correctly. The scope level makes sure that // code like this: // foo->deleteLater(); // qApp->processEvents(); // without passing QEvent::DeferredDelete // will not cause "foo" to be deleted before returning to the event loop.
// If the scope level is 0 while loopLevel != 0, we are called from a // non-conformant code path, and our best guess is that the scope level // should be 1. (Loop level 0 is special: it means that no event loops // are running.) int loopLevel = data->loopLevel; int scopeLevel = data->scopeLevel; if (scopeLevel == 0 && loopLevel != 0) scopeLevel = 1; static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel; }
// delete the event on exceptions to protect against memory leaks till the event is // properly owned in the postEventList QScopedPointer<QEvent> eventDeleter(event); Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type()); data->postEventList.addEvent(QPostEvent(receiver, event, priority)); eventDeleter.take(); event->posted = true; ++receiver->d_func()->postedEvents; data->canWait = false; locker.unlock();
QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire(); if (dispatcher) dispatcher->wakeUp(); }
-
判断事件接收对象是否为空
// 事件的接收者不能为空 if (receiver == nullptr) { qWarning("QCoreApplication::postEvent: Unexpected null receiver"); delete event; return; } -
将事件接收对象所在线程的post事件列表上锁,如果已经被锁了,就把事件删除掉,并返回,防止泄露。
// 对事件接受对象所在线程的事件处理列表上锁 auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver); if (!locker.threadData) { // posting during destruction? just delete the event to prevent a leak delete event; return; } -
将一些可以压缩的事件进行压缩,及多个事件压缩成只推送最后的一个事件。Qt界面的
update
就是这个操作,为了防止多次刷新导致卡顿,短时间内多次的调用update
可能只会刷新一次// if this is one of the compressible events, do compression // 将重复的事件,进行压缩 if (receiver->d_func()->postedEvents && self && self->compressEvent(event, receiver, &data->postEventList)) { Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event); return; } -
将事件插入接收对象所在线程的post事件列表中,并唤醒线程的事件调度器,来进行事件的处理。所以
postEvent
是非阻塞的,因为其只是把事件插入了线程的事件列表,唤醒事件调度器之后便返回。// delete the event on exceptions to protect against memory leaks till the event is // properly owned in the postEventList QScopedPointer<QEvent> eventDeleter(event); Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type()); data->postEventList.addEvent(QPostEvent(receiver, event, priority)); eventDeleter.take(); event->posted = true; ++receiver->d_func()->postedEvents; data->canWait = false; locker.unlock();
QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire(); if (dispatcher) dispatcher->wakeUp();
事件是如何处理的?
在Qt中,事件的接收者都是QObject
,而QObject
中事件处理是调用event
函数。如果当时对象不处理某个事件,就会将其转发到父类的event
进行处理。
而事件的处理,主要分为三个部分:
所以,在这一章节,我们同样一步一步的分析这三个点。
1|0事件循环是怎么遍历的?
int main(int argc, char *argv[]) { QApplication a(argc, argv);
MainWindow w; w.show(); return a.exec(); }
上面是一个经典的QtGUI程序的main函数,调用a.exec()
而看QApplication::exec
的源码,实际上就是开启了一个事件循环(QEventLoop
)。同样,我们去看QEventLoop::exec
的源码,进一步看处理事件的步骤是什么。
int QEventLoop::exec(ProcessEventsFlags flags) { ...
while (!d->exit.loadAcquire()) processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false; return d->returnCode.loadRelaxed(); }
上面可以看到,QEvenLoop::exec
里,是一个while
循环,循环的去调用processEvent
,而且设置了WaitForMoreEvents
就是说,如果没有事件,就阻塞等待。
阅读processEvent
,其调用了线程的事件调度器QAbstrctEventDispatcher
,而这个类是一个抽象基类,根据不同的平台,有不同的实现,我们以windows下(QEventDispatcherWin32
)的为例,接着分析事件处理的流程。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { Q_D(QEventDispatcherWin32);
...
// To prevent livelocks, send posted events once per iteration. // QCoreApplication::sendPostedEvents() takes care about recursions. sendPostedEvents();
... }
void QEventDispatcherWin32::sendPostedEvents() { Q_D(QEventDispatcherWin32);
if (d->sendPostedEventsTimerId != 0) KillTimer(d->internalHwnd, d->sendPostedEventsTimerId); d->sendPostedEventsTimerId = 0;
// Allow posting WM_QT_SENDPOSTEDEVENTS message. d->wakeUps.storeRelaxed(0);
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed()); }
可以看到,事件调度器最终还是调用了QCoreApplication
的sendPostEvents
void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type, QThreadData *data) { if (event_type == -1) { // we were called by an obsolete event dispatcher. event_type = 0; }
if (receiver && receiver->d_func()->threadData != data) { qWarning("QCoreApplication::sendPostedEvents: Cannot send " "posted events for objects in another thread"); return; }
...
// Exception-safe cleaning up without the need for a try/catch block struct CleanUp { QObject *receiver; int event_type; QThreadData *data; bool exceptionCaught;
inline CleanUp(QObject *receiver, int event_type, QThreadData *data) : receiver(receiver), event_type(event_type), data(data), exceptionCaught(true) {} inline ~CleanUp() { if (exceptionCaught) { // since we were interrupted, we need another pass to make sure we clean everything up data->canWait = false; }
--data->postEventList.recursion; if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher()) data->eventDispatcher.loadRelaxed()->wakeUp();
// clear the global list, i.e. remove everything that was // delivered. if (!event_type && !receiver && data->postEventList.startOffset >= 0) { const QPostEventList::iterator it = data->postEventList.begin(); data->postEventList.erase(it, it + data->postEventList.startOffset); data->postEventList.insertionOffset -= data->postEventList.startOffset; Q_ASSERT(data->postEventList.insertionOffset >= 0); data->postEventList.startOffset = 0; } } }; CleanUp cleanup(receiver, event_type, data);
while (i < data->postEventList.size()) { ...
// first, we diddle the event so that we can deliver // it, and that no one will try to touch it later. pe.event->posted = false; QEvent *e = pe.event; QObject * r = pe.receiver;
--r->d_func()->postedEvents; Q_ASSERT(r->d_func()->postedEvents >= 0);
// next, update the data structure so that we're ready // for the next event. const_cast<QPostEvent &>(pe).event = nullptr;
locker.unlock(); const auto relocker = qScopeGuard([&locker] { locker.lock(); });
QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)
// after all that work, it's time to deliver the event. QCoreApplication::sendEvent(r, e);
// careful when adding anything below this point - the // sendEvent() call might invalidate any invariants this // function depends on. }
cleanup.exceptionCaught = false; }
我们一个一个的分块分析:
-
判断是否在一个线程
if (receiver && receiver->d_func()->threadData != data) { qWarning("QCoreApplication::sendPostedEvents: Cannot send " "posted events for objects in another thread"); return; } -
一个有意思的异常安全的处理,不需要try/catch块
// Exception-safe cleaning up without the need for a try/catch block struct CleanUp { QObject *receiver; int event_type; QThreadData *data; bool exceptionCaught;
inline CleanUp(QObject *receiver, int event_type, QThreadData *data) : receiver(receiver), event_type(event_type), data(data), exceptionCaught(true) {} inline ~CleanUp() { if (exceptionCaught) { // since we were interrupted, we need another pass to make sure we clean everything up data->canWait = false; }
--data->postEventList.recursion; if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher()) data->eventDispatcher.loadRelaxed()->wakeUp();
// clear the global list, i.e. remove everything that was // delivered. if (!event_type && !receiver && data->postEventList.startOffset >= 0) { const QPostEventList::iterator it = data->postEventList.begin(); data->postEventList.erase(it, it + data->postEventList.startOffset); data->postEventList.insertionOffset -= data->postEventList.startOffset; Q_ASSERT(data->postEventList.insertionOffset >= 0); data->postEventList.startOffset = 0; } } }; CleanUp cleanup(receiver, event_type, data);
定义了一个结构体CleanUp
,结构体的析构函数(~CleanUp
)保存了函数退出时需要执行的清理操作。然后在栈上创建了一个结构体对象,遍历事件列表时,异常退出,那么就会调用自动调用~CleanUp
的析构函数。
-
将事件发送出去(
sendEvent
)while (i < data->postEventList.size()) { ...
// first, we diddle the event so that we can deliver // it, and that no one will try to touch it later. pe.event->posted = false; QEvent *e = pe.event; QObject * r = pe.receiver;
--r->d_func()->postedEvents; Q_ASSERT(r->d_func()->postedEvents >= 0);
// next, update the data structure so that we're ready // for the next event. const_cast<QPostEvent &>(pe).event = nullptr;
locker.unlock(); const auto relocker = qScopeGuard([&locker] { locker.lock(); });
QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)
// after all that work, it's time to deliver the event. QCoreApplication::sendEvent(r, e);
// careful when adding anything below this point - the // sendEvent() call might invalidate any invariants this // function depends on. }
可以看到,核心还是调用sendEvent
将事件发送出去,而前面我们对sendEvent
的源码分析我们可以看到,事件先是经过事件过滤器,再经过对象的event函数,来进行事件的处理。所以就引出我们的下一个话题:事件过滤器
1|0事件过滤器
在实际应用中,我们经常要将某一个窗口部件的某个事件如鼠标滑轮滚动拦截,然后执行我们自己想要的操作。这个时候,我们就可以用到事件过滤器(EventFilter
**) **
首先,我们需要自己编写一个eventFilter
函数,
bool Class::eventFilter(QObject* watcher, QEvent* event) { //以过滤鼠标滚轮事件为例 if (object == m_watcherObject && event->type() == QEvent::Wheel) { // do something return true; }
QWidget::eventFilter(watcher, event); }
然后,我们需要为要拦截的某个窗口部件,安装事件过滤器
void Class::initUI() { QWidget* m_watcherObject = new QWidget(this); // 为对象安装一个事件过滤器 m_watcherObject->installEventFilterr(this); }
initUI();
那么一个对象安装的多个事件过滤器,会以什么样的顺序触发呢?我们在前面的讲过,后安装的事件过滤器会先触发,这一点,我们可以在源码里得到佐证:
void QObject::installEventFilter(QObject *obj) { Q_D(QObject); if (!obj) return; if (d->threadData != obj->d_func()->threadData) { qWarning("QObject::installEventFilter(): Cannot filter events for objects in a different thread."); return; }
if (!d->extraData) d->extraData = new QObjectPrivate::ExtraData;
// clean up unused items in the list d->extraData->eventFilters.removeAll((QObject*)nullptr); d->extraData->eventFilters.removeAll(obj); d->extraData->eventFilters.prepend(obj); }
可以清楚的看到,事件过滤器,是以prepend
的形式被添加进事件过滤器列表的。
那么,当有鼠标滚轮事件触发的时候,我们可以看到sendEvent
会优先走到事件过滤器里,如果eventFilter
返回一个true,那么事件就不会被继续派发,否则,将会将事件发送到其他的事件过滤器里进行处理,如果其他的事件过滤器均对该事件不进行处理,那么事件将会继续往下派发,走到事件的处理函数event
1|0event
接下来,就到了事件处理的最后一站,event
函数,这个函数比较简单,我们可以自己重写这个函数,对事件进行自定义的处理。
bool Class::event(QEvent *e) { switch (e->type()) { case QEvent::Whell: // do something return true;
default: if (e->type() >= QEvent::User) { customEvent(e); break; } return false; } return true; }
夹带私货时间
- 之前有说到
processEvent
,添加一个小经验。当我们有时候不得不在主线程循环执行很耗时的操作的时候,这个时候,界面就会刷新不过来,就会导致界面卡顿,影响使用。但是,我们可以在这个循环里,手动调用qApp->processEvent()
,这样就可以手动调用处理掉所有的事件,就可以解决卡顿的问题。
__EOF__
Recommend
-
132
一、为什么要制作《服务器硬件工程师从入门到精通》系列课程?做为一名服务器硬件工程师,在刚刚接触到服务器硬件技术这块时,我自己曾经深受困扰,特别希望能够找到一套系统的服务器硬件技术指导教程,但是现实很无奈,在书店里..
-
84
我们经常说JS的事件循环有微观队列和宏观队列,所有的异步事件都会放到这两个队列里面等待执行,并且微观队列要先于宏观队列执行。实际上事件循环是多线程的一种工作方式。通常为了提高运行效率会新起一条或多条线程进行并行运算,然后算...
-
77
本文主要分享Netty中事件循环机制的实现。 源码分析基于Netty 4.1 EventLoop 前面分享服务端和客户端启动过程的文章中说过,Netty通过事件循环机制(Eve...
-
5
TiDB Operator 源码阅读 (三) 编排组件控制循环TiDB Robot开源分布式数据库 TiDB
-
7
TiDB Operator 源码阅读 (四) 组件的控制循环TiDB Robot开源分布式数据库 TiDB
-
2
Qt QComboBox之setEditable和currentTextChanged以及其源码分析 最近做了一个QComboBox里有选项,然后选中选项之后就会自动触发条件搜索。然后我发现,在我初始化comboBox时,由于信号连接的原因会触发这个currentTextChanged信号。代码大致如下:
-
3
Qt 实现文字输入框,带字数限制 核心的点在于,限制输入的字数;主要的方法为创建一个组合窗口 t...
-
7
Qt中MVC的M(Model)简单介绍 Qt有自己的MVC框架,分别是model(模型)、view(视图)、delegate(委托),这篇文章,简单的介绍以下Qt中有关model(模型)的类以及一些基本的使用。 Qt官方的文档已经很详...
-
4
信号槽连接 信号槽的连接,其实内部本质还是一个回调函数,主要是维护了信号发送Object的元对象里一个连接的列表。调用connect函数时,将槽的一系列信息,封装成一个Connection,在发送信号时,通过这个...
-
8
对象树管理 个人经验总结,如有错误或遗漏,欢迎各位大佬指正 😃 设置父对象的作用 众所周知,Qt中,有为对象设置父对象的方法——setParent。...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK