Qt中的三个exec之间有什么联系

描述

一、导读

在Qt中,常见到三个exec,第一个是QApplication::exec(),第二个是QEventLoop::exec,第三个是QThread::exec()。本文从源码角度来看看这三个exec()。

QApplication::exec()是QApplication类下的一个静态成员函数,该函数用于进入主事件循环。

QEventLoop::exec是QEventLoop类下的一个公共成员函数,用于进入主事件循环。

QThread::exec()是QThread类下的一个受保护的成员函数,也是用于进入事件循环。

都是进入事件循环,他们之间有什么联系呢,接着后面的分析。

二、QApplication::exec()

在实际开发中,必须调用QApplication::exec()来启动事件处理,主事件循环会从窗口系统接收事件,并将这些事件分派给应用程序小部件。在调用exec()之前不能发生任何用户交互,但是存在一种特殊的情况:QMessageBox这样的模态小部件可以在调用exec()之前使用,因为模态小部件会调用exec()来启动本地事件循环。

从源码角度,QApplication::exec()会调用QGuiApplication::exec(),QGuiApplication::exec()会调用QCoreApplication::exec():

 

int QCoreApplication::exec()
{
    //检查exec实例
    if (!QCoreApplicationPrivate::checkInstance("exec"))
        return -1;
 
    //获取线程数据QThreadData
    QThreadData *threadData = self->d_func()->threadData;
    
    //检查该函数的调用是否在主线程中,如果不是,则返回。
    if (threadData != QThreadData::current()) {
        qWarning("%s: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }
    //检查是否存在事件循环,如果存在,则返回,否则继续后续操作。
    if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication: The event loop is already running");
        return -1;
    }
 
    threadData->quitNow = false;
    //创建QEventLoop事件循环对象
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    //启动事件循环
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;

    if (self)
        self->d_func()->execCleanup();

    return returnCode;
}

 

从上述源码可见,QApplication的exec()经过层层调用,最终是使用QEventLoop实现事件循环。

QApplication::exec()用于启动应用程序的事件循环,让应用程序能得以启动运行并接收事件。

『备注,执行应用清理的优雅方式』:

建议将清理代码连接到aboutToQuit()信号,而不是放在应用程序的main()函数中。这是因为,在某些平台上,QApplication::exec()调用可能不会返回。例如,在Windows平台上,当用户注销时,系统会在Qt关闭所有顶级窗口后终止该进程。因此,不能保证应用程序有足够的时间退出事件循环,并在QApplication::exec()调用之后,即在main()函数的末尾执行代码。

在QCoreApplication::exec()函数实现中的这几行代码:

 

    if (threadData != QThreadData::current()) {
        qWarning("%s: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }

 

引发出另一个有趣的知识点,那就是:在Qt多线程开发中,需要注意不要阻塞GUI线程,那么哪个是GUI线程呢?从上述源码可以明确知道:QApplication a(argc, argv);所在线程就是GUI线程。

三、QThread::exec()

在多线程应用设计中,QThread::exec()用于为当前线程启动一个新的事件循环,为存在于该线程中的对象交付事件。在源码中,QThread::exec()实现如下:

 

int QThread::exec()
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);
    d->data->quitNow = false;
    if (d->exited) {
        d->exited = false;
        return d->returnCode;
    }
    locker.unlock();
 //创建QEventLoop事件循环。
    QEventLoop eventLoop;
    int returnCode = eventLoop.exec();

    locker.relock();
    d->exited = false;
    d->returnCode = -1;
    return returnCode;
}

 

从源码角度,也可见QThread::exec()实现中也调用到QEventLoop的exec()。

四、QEventLoop::exec()

QEventLoop::exec()用于进入主事件循环并等待直到exit()被调用。在调用该函数时需要传入一个flags,如果指定了标志,则只处理标志允许的类型的事件,Qt中支持以下几种标志:

序号 标志类型 描述
1 QEventLoop::AllEvents 所有事件
2 QEventLoop::ExcludeUserInputEvents 不处理用户输入事件,例如ButtonPress和KeyPress。
3 QEventLoop::ExcludeSocketNotifiers 不要处理套接字通知事件。
4 QEventLoop::WaitForMoreEvents 如果没有可用的挂起事件,则等待事件。

注意,没有被传递的事件不会被丢弃,这些事件将在下次传入不过滤事件的标志调用procesvents()时被传递。

从源码角度,QEventLoop::exec()实现如下:

 

int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    auto threadData = d->threadData.loadRelaxed();

    //we need to protect from race condition with QThread::exit
    QMutexLocker locker(&static_cast(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex);
    if (threadData->quitNow)
        return -1;

    if (d->inExec) {
        qWarning("QEventLoop: instance %p has already called exec()", this);
        return -1;
    }

    struct LoopReference {
        QEventLoopPrivate *d;
        QMutexLocker &locker;

        bool exceptionCaught;
        LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)
        {
            d->inExec = true;
            d->exit.storeRelease(false);

            auto threadData = d->threadData.loadRelaxed();
            ++threadData->loopLevel;
            threadData->eventLoops.push(d->q_func());

            locker.unlock();
        }

        ~LoopReference()
        {
            if (exceptionCaught) {
                qWarning("Qt has caught an exception thrown from an event handler. Throwing
"
                         "exceptions from an event handler is not supported in Qt.
"
                         "You must not let any exception whatsoever propagate through Qt code.
"
                         "If that is not possible, in Qt 5 you must at least reimplement
"
                         "QCoreApplication::notify() and catch all exceptions there.
");
            }
            locker.relock();
            auto threadData = d->threadData.loadRelaxed();
            QEventLoop *eventLoop = threadData->eventLoops.pop();
            Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
            Q_UNUSED(eventLoop); // --release warning
            d->inExec = false;
            --threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);

    // 当进入一个新的事件循环时,删除已发布的exit事件
    QCoreApplication *app = QCoreApplication::instance();
    if (app && app->thread() == thread())
        QCoreApplication::removePostedEvents(app, QEvent::Quit);

#ifdef Q_OS_WASM
    // Partial support for nested event loops: Make the runtime throw a JavaSrcript
    // exception, which returns control to the browser while preserving the C++ stack.
    // Event processing then continues as normal. The sleep call below never returns.
    // QTBUG-70185
    if (threadData->loopLevel > 1)
        emscripten_sleep(1);
#endif

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

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

 

从上述源码可知,QEventLoop::exec()本质会调用 processEvents()分发事件。 processEvents()实现如下:

源码

从上图所示代码中,会调用QAbstractEventDispatcher::processEvents()实现事件分发。QAbstractEventDispatcher类提供了一个管理Qt事件队列的接口,它从窗口系统和其他地方接收事件。然后它将这些事件发送到QCoreApplication或QApplication实例进行处理和交付。

  审核编辑:汤梓红
 
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分