Qt source code reading (4) event loop

event system

The article is my own understanding, if there is something wrong with my understanding, please correct me.


Qt's event loop should be a point that all Qters cannot avoid. Therefore, in this blog, let's understand some parts of the source code about the event loop in Qt.
First throw a few questions, according to the source code, the following one by one analysis.

  1. What is the event loop?
  2. How did the event occur?
  3. How are events handled?

What is an event loop?

My personal understanding of the Qt event loop is that the event loop is a queue to cycle through events. When there is an event in the queue, it will process the event, and if there is no event, it will block and wait.

How are events generated?

Event generation can be divided into two types:

  1. Generated outside the program
  2. internally generated

Events generated outside the program mainly refer to events generated by the system, such as mouse button press (MouseButtonPress), key press (KeyPress), etc. Qt captures system events, then encapsulates system events into its own class, and then encapsulates the QEventevent send it out.

The events generated inside the program mainly mean that we manually create an event in the code, and then send the event to the event loop through sendEvent/ . postEventAnd the difference is that one is blocking ( ) and the sendEventother is non-blocking ( ).postEventsendEventpostEvent

Let's combine the source code analysis to see what we did sendEventand postEventwhat caused one to be blocked and the other to be non-blocked.

sendEvent

The complete source code is as follows:

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

As you can see, this function sendEventis callednotifyInternal2

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

further traced to its doNotifyfunction

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

then toQCoreApplicationPrivate::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;
}

Then we can see that there are several main processes:

  1. Determine whether QCoreApplication has an event filter installed, and if so, send the signal to the event filter, and the event filter will process the event.

    // 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. Determine whether the event accepting object is installed or not, and if so, send the signal to the event filter.

    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event)) {
          
          
        filtered = true;
        return filtered;
    }
    

    The specific code for traversing the event filter installed by the event accepting object is as follows:

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

    We can see that as long as an event is successfully processed by an event filter, subsequent event filters will not be responded to. At the same time, refer to the Qt help manual mentioned:

    If multiple event filters are installed on a single object, the filter that was installed last is activated first.

    Event filters inserted later will be responded to first. Specifically install the event filter, we will analyze it later.

  3. Directly call eventthe function of the event accepting object for processing. Because it is the object of the direct call event, so sendEventthe function will block and wait.

        // deliver the event
        // 直接调用对象的event函数,所以是阻塞的
        consumed = receiver->event(event);
        return consumed
    

postEvent

The complete code is as follows:

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. Determine whether the event receiving object is empty

    // 事件的接收者不能为空
    if (receiver == nullptr) {
          
          
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    }
    
  2. Lock the post event list of the thread where the event receiving object is located. If it is already locked, delete the event and return to prevent leakage.

    // 对事件接受对象所在线程的事件处理列表上锁
    auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
    if (!locker.threadData) {
          
          
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    }
    
  3. Compress some events that can be compressed, and compress multiple events into only the last event. This is the operation of the Qt interface update. In order to prevent multiple refreshes from causing freezes, multiple calls in a short period of time updatemay only be refreshed once.

    // 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. Insert the event into the post event list of the thread where the receiving object is located, and wake up the event scheduler of the thread to process the event. So postEventit is non-blocking, because it just inserts the event into the thread's event list, and returns after waking up the event scheduler .

        // 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();
    

How are events handled?

In Qt, the receivers of the event are all QObject, and QObjectthe event processing is to call eventthe function. If the object does not handle an event at that time, it will be forwarded to the parent class eventfor processing.
The event processing is mainly divided into three parts:

  1. First, the event loop traverses the event
  2. Then judge whether the event accepting object has installed the event filter ( installEventFilter), and if so, throw the event to the event filter ( eventFilter) for processing.
  3. If no event filter is installed or the event filter does not process the event, the event will be further forwarded to the function eventfor processing.

Therefore, in this chapter, we also analyze these three points step by step.

How is the event loop traversed?

int main(int argc, char *argv[])
{
    
    
    QApplication a(argc, argv);

    MainWindow w;
    w.show();
    return a.exec();
}

The above is the main function of a classic QtGUI program, calla.exec()

int QCoreApplication::exec()
{
    
    
    ...
    
    threadData->quitNow = false;
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    int returnCode = eventLoop.exec();
    
    ...
}

And looking QApplication::execat the source code, it actually starts an event loop ( QEventLoop). Similarly, let's look at QEventLoop::execthe source code, and further look at the steps to process the event.

int QEventLoop::exec(ProcessEventsFlags flags)
{
    
    
    ...

    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);

    ref.exceptionCaught = false;
    return d->returnCode.loadRelaxed();
}

As you can see above, QEvenLoop::execthere is a whileloop, which is called cyclically processEvent, and it is set WaitForMoreEvents, that is, if there is no event, it will block and wait.

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

Read processEvent, it calls the event scheduler of the thread QAbstrctEventDispatcher, and this class is an abstract base class, which has different implementations according to different platforms. Let's take ( QEventDispatcherWin32) under windows as an example, and then analyze the process of event processing.

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

As you can see, the event scheduler finally QCoreApplicationcallssendPostEvents

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

Let's analyze the blocks one by one:

  1. Determine whether in a thread

    if (receiver && receiver->d_func()->threadData != data) {
          
          
        qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                 "posted events for objects in another thread");
        return;
    }
    
  2. An interesting exception-safe handling that doesn't require a try/catch block

    // 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);
    

A structure is defined CleanUp, and the destructor ( ) of the structure ~CleanUpsaves the cleanup operations that need to be performed when the function exits. Then a structure object is created on the stack. When traversing the event list, if an exception exits, the automatically called ~CleanUpdestructor will be called.

  1. send event out( 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.
        }
    

It can be seen that the core still calls sendEventto send the event , and sendEventwe can see from the previous source code analysis that the event first passes through the event filter, and then passes through the event function of the object to process the event. So that leads to our next topic: event filters

event filter

In practical applications, we often need to intercept a certain event of a certain widget, such as mouse wheel scrolling, and then perform the operation we want. At this time, we can use the event filter (EventFilter **) **
First, we need to write a eventFilterfunction ourselves,

bool Class::eventFilter(QObject* watcher, QEvent* event)
{
    
    
	//以过滤鼠标滚轮事件为例
    if (object == m_watcherObject && event->type() == QEvent::Wheel) {
    
    
    	// do something
        return true;       
    }

    QWidget::eventFilter(watcher, event);
}

Then, we need to install an event filter for a certain widget to be intercepted

void Class::initUI() 
{
    
    
	QWidget* m_watcherObject = new QWidget(this);
    // 为对象安装一个事件过滤器
	m_watcherObject->installEventFilterr(this);
}

initUI();

So in what order will multiple event filters installed by an object be triggered? As we said before, the event filter installed later will be triggered first . We can get evidence of this in the source code:

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

It can be clearly seen that the event filter is prependadded to the event filter list in the form of .
Then, when a mouse wheel event is triggered, we can see that sendEventit will go to the event filter first. If eventFiltera true is returned, the event will not be dispatched, otherwise, the event will be sent to other event filters If other event filters do not process the event, the event will continue to be dispatched and go to the event processing functionevent

event

Next, we come to the last stop of event processing, eventthe function. This function is relatively simple. We can rewrite this function by ourselves to perform custom processing on the 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;
}

Carrying private goods time

  1. As mentioned before processEvent, add a little experience. When we sometimes have to perform time-consuming operations in the main thread loop, at this time, the interface will not be refreshed, which will cause the interface to freeze and affect the use. However, we can manually call it in this loop qApp->processEvent(), so that all events can be manually called and processed, and the problem of stuck can be solved .

Guess you like

Origin blog.csdn.net/qq_44723937/article/details/129865562