1

Qt源码阅读(四) 事件循环 - 师从名剑山

 1 year ago
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.
neoserver,ios ssh client

事件系统

文章为本人理解,如有理解不到位之处,烦请各位指正。

Qt的事件循环,应该是所有Qter都避不开的一个点,所以,这篇博客,咱们来了解源码中一些关于Qt中事件循环的部分。
先抛出几个疑问,根据源代码,下面一一进行解析。

什么是事件循环?

对于Qt事件循环个人理解是,事件循环是一个队列去循环处理事件。当队列中有事件时,则去处理事件,如果没有事件时,则会阻塞等待。

事件是如何产生的?

事件的产生可以分为两种:

  1. 程序外部产生
  2. 程序内部产生

程序外部所产生的事件主要是指系统产生的事件,比如说鼠标按下(MouseButtonPress)、按键按下(KeyPress)等,Qt捕捉系统的事件,然后将系统事件封装成自己的QEvent类,再将事件发送出去。

程序内部产生的事件主要指我们在代码里,手动创建一个事件,然后将事件通过sendEvent/postEvent,来发送到事件循环中。而sendEventpostEvent区别又在于一个是阻塞的(sendEvent)一个是非阻塞的(postEvent)。

我们结合源码分析,看一下sendEventpostEvent分别干了什么导致一个是阻塞的一个是非阻塞的。

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这个函数



bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event) { ... // Qt enforces the rule that events can only be sent to objects in // the current thread, so receiver->d_func()->threadData is // equivalent to QThreadData::current(), just without the function // call overhead. // 事件只能在同一个线程被send QObjectPrivate *d = receiver->d_func(); QThreadData *threadData = d->threadData; QScopedScopeLevelCounter scopeLevelCounter(threadData); if (!selfRequired) return doNotify(receiver, event); return self->notify(receiver, event); }

进一步跟踪到其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; }

然后我们可以看到主要有几个流程:

  1. 判断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; }
  2. 判断事件接受对象,有没有安装事件过滤器,有就将信号发送到事件过滤器。

    // 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.

    后插入的事件过滤器会被优先响应。 具体安装事件过滤器,我们在后面进行分析。

  3. 直接调用事件接受对象的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(); }

  1. 判断事件接收对象是否为空

    // 事件的接收者不能为空 if (receiver == nullptr) { qWarning("QCoreApplication::postEvent: Unexpected null receiver"); delete event; return; }
  2. 将事件接收对象所在线程的post事件列表上锁,如果已经被锁了,就把事件删除掉,并返回,防止泄露。

    // 对事件接受对象所在线程的事件处理列表上锁 auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver); if (!locker.threadData) { // posting during destruction? just delete the event to prevent a leak delete event; return; }
  3. 将一些可以压缩的事件进行压缩,及多个事件压缩成只推送最后的一个事件。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; }
  4. 将事件插入接收对象所在线程的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()



int QCoreApplication::exec() { ... threadData->quitNow = false; QEventLoop eventLoop; self->d_func()->in_exec = true; self->d_func()->aboutToQuitEmitted = false; int returnCode = eventLoop.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就是说,如果没有事件,就阻塞等待。



void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int ms) { // ### Qt 6: consider splitting this method into a public and a private // one, so that a user-invoked processEvents can be detected // and handled properly. QThreadData *data = QThreadData::current(); if (!data->hasEventDispatcher()) return; QElapsedTimer start; start.start(); while (data->eventDispatcher.loadRelaxed()->processEvents(flags & ~QEventLoop::WaitForMoreEvents)) { if (start.elapsed() > ms) break; } }

阅读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()); }

可以看到,事件调度器最终还是调用了QCoreApplicationsendPostEvents



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; }

我们一个一个的分块分析:

  1. 判断是否在一个线程

    if (receiver && receiver->d_func()->threadData != data) { qWarning("QCoreApplication::sendPostedEvents: Cannot send " "posted events for objects in another thread"); return; }
  2. 一个有意思的异常安全的处理,不需要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的析构函数。

  1. 将事件发送出去(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; }

夹带私货时间

  1. 之前有说到processEvent,添加一个小经验。当我们有时候不得不在主线程循环执行很耗时的操作的时候,这个时候,界面就会刷新不过来,就会导致界面卡顿,影响使用。但是,我们可以在这个循环里,手动调用qApp->processEvent(),这样就可以手动调用处理掉所有的事件,就可以解决卡顿的问题

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK