Qt event cycle process analysis

Qt event cycle process analysis

This article will explain the following questions:

  1. Why does Qt application start with QCoreApplication app(argc, argv) and return app in main() function Exec() end?
  2. Why does while(true) cause the CPU to be fully loaded when both are loops? The event loop of Qt will not.
  3. Why can blocking program execution through QEventLoop::exec() avoid program jamming?

Main event loop

In general, the main() function of Qt application will approximate the following form:

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    ......
    return app.exec();
}

In the QCoreApplication app(argc, argv), a QCoreApplication object is created and initialized. The most important thing is to set some thread related data (QThreadData), such as event scheduler (QAbstractEventDispatcher).
And app Exec () is to start an event loop to distribute events. If there is no event loop or the event loop is not started, the object will never receive an event.
The event loop started by QCoreApplication is also called the main event loop.

After omitting most of the code, we can clearly see how QCoreApplication::exec() starts an event loop:

int QCoreApplication::exec()
{
    ......
    QEventLoop eventLoop;
    ......
    int returnCode = eventLoop.exec();
    ......
    return returnCode;
}

event loop

The following code shows that QEventLoop::exec() distributes events in the event queue by calling QEventLoop::processEvents() repeatedly.

int QEventLoop::exec(ProcessEventsFlags flags = AllEvents))
{
    Q_D(QEventLoop);
    ......
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);
    ......
    return d->returnCode.load();
}

The event scheduler finally completes the event distribution. Through the following code, we can see that the event scheduler exists in the relevant data of their respective threads. That is, each thread can have and only use its own event scheduler.

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->hasEventDispatcher())
        return false;
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}

Here we can see why the program is blocked by QEventLoop::exec(), but the program will not get stuck. Because QEventLoop::exec() opens a new event loop to distribute events, and all event loops on the same thread use the same event scheduler.

event scheduler

Event scheduler depends on the implementation of each platform, and the implementation of event scheduler on each platform is different. We take qeventdispatcher UNIX as an example for analysis.

bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherUNIX);
    ......
    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
    ......
    timespec *tm = nullptr;
    ......
    d->pollfds.append(d->threadPipe.prepare());

    int nevents = 0;

    switch (qt_safe_poll(d->pollfds.data(), d->pollfds.size(), tm)) {
    ......
    }
    return (nevents > 0);
}

void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
                                               QThreadData *data)
{
    while (i < data->postEventList.size()) {
        ......
        const QPostEvent &pe = data->postEventList.at(i);
        ++i;
        ......
        QEvent *e = pe.event;
        QObject * r = pe.receiver;
        ......
        QCoreApplication::sendEvent(r, e);
    }
    ......
}

Qcoreapplicationprivate:: sendpostedevents (0, 0, D - > threaddata) will dispatch all events in the current thread event queue. When the event is dispatched, will it enter the next event queue dispatch cycle immediately?
This is not the case. In qeventdispatcher UNIX, the member threadPipe (QThreadPipe) of its member d (qeventdispatcher unixprivate) obtains a file descriptor through the eventfd() system call, and passes QT in qeventdispatcher UNIX:: processevents()_ safe_ poll() waits for its data to be readable (POLLIN). In this process, the thread enters the kernel state and waits for the event to occur (we don't know how to implement the poll () call anyway. In short, it doesn't occupy CPU resources), so it won't occupy CPU resources.
Actually qt_safe_poll() also monitors the status of file descriptors monitored by QSocketNotifier, etc. When the event specified by these file descriptors occurs, it will also exit the kernel state and enter the next event cycle.

Send event

As mentioned above, qeventdispatcher UNIX:: processevents() will wait for the event to occur, so as to exit the kernel state and enter the next event cycle. As follows:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
    ......
    QThreadData * volatile * pdata = &receiver->d_func()->threadData;
    QThreadData *data = *pdata;
    ......
    data->postEventList.addEvent(QPostEvent(receiver, event, priority));
    ......
    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
    if (dispatcher)
        dispatcher->wakeUp();
}

When an event is sent through QCoreApplication::postEvent(), it will be added to the event queue and wake up the event scheduler. Specifically, write data to the file descriptor obtained by the eventfd() system call to make its status data readable (POLLIN).

void QEventDispatcherUNIX::wakeUp()
{
    Q_D(QEventDispatcherUNIX);
    d->threadPipe.wakeUp();
}

void QThreadPipe::wakeUp()
{
    if (wakeUps.testAndSetAcquire(0, 1)) {
#ifndef QT_NO_EVENTFD
        if (fds[1] == -1) {
            // eventfd
            eventfd_t value = 1;
            int ret;
            EINTR_LOOP(ret, eventfd_write(fds[0], value));
            return;
        }
#endif
        char c = 0;
        qt_safe_write(fds[1], &c, 1);
    }
}

Keywords: Qt

Added by hairytea on Thu, 10 Feb 2022 16:00:49 +0200