exec()
谈起一个标准的Qt-gui程序,在启动时我们会coding如下几行简洁的代码:
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
在这里我们首先考虑第一个问题,如果主程序中没有调用 a.exec()
,在编译运行时会发生什么?
~~ one thousand years later~~
对,也许你已经非常清楚了,你定义的widget
窗口竟然没有显示,或者你以自己如同高速摄像机的神器双眼,炯炯侠般捕获到窗口突然的闪现然后消失。这时候你明白,a.exec()
是保证窗口程序不立即退出的重要保证。
你的猜想很对,同时我要告诉你的是,a.exec()
做的工作显然比这要多很多。
QObject
|__QCoreApplication 源码路径:qtbase\src\corelib\kernel\qcoreapplication.cpp
|__QGuiApplication 源码路径:qtbase\src\gui\kernel\qguiapplication.cpp
|_QApplication 源码路径: qtbase\src\widgets\kernel\qapplication.cpp
图2.1 QApplication 继承类图
我们采用源码调试的方式,倒推来看我们的关注点。
QApplication a(argc, argv);
对象实例化,构造函数如下:
#ifdef Q_QDOC
QApplication::QApplication(int &argc, char **argv)
#else
QApplication::QApplication(int &argc, char **argv, int _internal)
#endif
: QGuiApplication(*new QApplicationPrivate(argc, argv, _internal))
{
Q_D(QApplication);
d->init(); /// 资源初始化
}
a.exec()
int QCoreApplication::exec()
{
// [1]
if (!QCoreApplicationPrivate::checkInstance("exec"))
return -1;
// [2]
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
// [3]
if (!threadData->eventLoops.isEmpty()) {
qWarning("QCoreApplication::exec: The event loop is already running");
return -1;
}
// [4]
threadData->quitNow = false;
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
int returnCode = eventLoop.exec();
threadData->quitNow = false;
// [5]
if (self)
self->d_func()->execCleanup();
return returnCode;
}
下面掰开了,揉碎了,逐行扒光了解读一番:
if (!QCoreApplicationPrivate::checkInstance("exec"))
return -1;
判断QCoreApplication
对象是否已经实例化,否则打印输出错误信息并退出。
bool QCoreApplicationPrivate::checkInstance(const char *function)
{
bool b = (QCoreApplication::self != nullptr);
if (!b)
qWarning("QApplication::%s: Please instantiate the QApplication object first", function);
return b;
}
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
self->d_func()
,有点眼熟啊,这不就是Qt的孪生指针刺客 :q
指针和d
指针 中d
指针么?对这一块内容和知识不太熟悉的朋友,回头去看看博主曾经写过的相关内容 《Qt : d指针和q指针?》
我们回过头看看QCoreApplication
是如何申明的:
class Q_CORE_EXPORT QCoreApplication
#ifndef QT_NO_QOBJECT
: public QObject
#endif
{
Q_DECLARE_PRIVATE(QCoreApplication)
protected:
QCoreApplication(QCoreApplicationPrivate &p);
// ...
#ifdef QT_NO_QOBJECT
QScopedPointer<QCoreApplicationPrivate> d_ptr;
#endif
// ...
};
#define Q_CAST_IGNORE_ALIGN(body) QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wcast-align") body QT_WARNING_POP
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr));) } \
inline const Class##Private* d_func() const \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));) } \
friend class Class##Private;
#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
inline Class##Private* d_func() \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(Dptr));) } \
inline const Class##Private* d_func() const \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(Dptr));) } \
friend class Class##Private;
ok,读到这儿,我们先收起更深的好奇之心,我们拿到了我们暂时需要知道的, self->d_func()
获取到了 QCoreApplicationPrivate
的成员变量指针,即 QScopedPointer<QCoreApplicationPrivate> d_ptr;
class Q_CORE_EXPORT QCoreApplicationPrivate
#ifndef QT_NO_QOBJECT
: public QObjectPrivate
#endif
{
// ...
};
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
public:
// ....
ExtraData *extraData; // extra data set by the user
QThreadData *threadData; // id of the thread that owns the object
};
喔嚯.回过头来再看这一段.
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
这是是干啥啊,获取当前对象所在线程的id,哪个对象?QCoreApplication
对象或者它的子类对象QApplication a;
并判断当前线程id与对象所在线程id是否相同,不同则会给出错误提示并退出。
所以,这里有个知识点我们必须记住:QCoreApplication
及其子类,仅且只能在主线程中实例化!!!
我们先熟悉下 QThreadData 类
class QThreadData
{
public:
QThreadData(int initialRefCount = 1);
~QThreadData();
static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
#ifdef Q_OS_WINRT
static void setMainThread();
#endif
static void clearCurrentThreadData();
static QThreadData *get2(QThread *thread)
{ Q_ASSERT_X(thread != nullptr, "QThread", "internal error"); return thread->d_func()->data; }
void ref();
void deref();
inline bool hasEventDispatcher() const
{ return eventDispatcher.loadRelaxed() != nullptr; }
QAbstractEventDispatcher *createEventDispatcher();
QAbstractEventDispatcher *ensureEventDispatcher()
{
QAbstractEventDispatcher *ed = eventDispatcher.loadRelaxed();
if (Q_LIKELY(ed))
return ed;
return createEventDispatcher();
}
bool canWaitLocked()
{
QMutexLocker locker(&postEventList.mutex);
return canWait;
}
// This class provides per-thread (by way of being a QThreadData
// member) storage for qFlagLocation()
class FlaggedDebugSignatures
{
static const uint Count = 2;
uint idx;
const char* locations[Count];
public:
FlaggedDebugSignatures() : idx(0)
{ std::fill_n(locations, Count, static_cast<char*>(nullptr)); }
void store(const char* method)
{ locations[idx++ % Count] = method; }
bool contains(const char *method) const
{ return std::find(locations, locations + Count, method) != locations + Count; }
};
private:
QAtomicInt _ref;
public:
int loopLevel;
int scopeLevel;
QStack<QEventLoop *> eventLoops; 事件循环的主角来了
QPostEventList postEventList;
QAtomicPointer<QThread> thread;
QAtomicPointer<void> threadId;
QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
QVector<void *> tls;
FlaggedDebugSignatures flaggedSignatures;
bool quitNow;
bool canWait;
bool isAdopted;
bool requiresCoreApplication;
};
QStack<QEventLoop *> eventLoops;
是 QThreadData 类
中的一个成员变量,它是一个栈,用于存储 QEventLoop
对象的指针。
在 Qt
中,每个 QThread
都有一个与之关联的事件循环(QEventLoop)。事件循环是 GUI 程序的核心,它用于接收和处理各种事件,如用户输入、定时器事件、网络事件等。
然而,一个线程不仅可以有一个主事件循环,还可以有一个或多个嵌套的事件循环。例如,当你在一个线程中调用 QEventLoop::exec()
时,你就创建了一个新的事件循环,并将其推入到 eventLoops
栈中。当 QEventLoop::exit() 被调用时,当前的事件循环会结束,并从 eventLoops
栈中弹出。
eventLoops
栈的顶部始终是当前线程的当前事件循环。这意味着,当你在一个线程中调用 QCoreApplication::processEvents()
时,Qt 会处理 eventLoops
栈顶的事件循环中的事件。
总的来说,QStack<QEventLoop *> eventLoops
成员变量用于存储和管理一个线程中的所有事件循环。
if (!threadData->eventLoops.isEmpty()) {
qWarning("QCoreApplication::exec: The event loop is already running");
return -1;
}
这段,如果栈容器中的事件数量为空,则说明QCoreApplication
事件循环还没有运行,否则,说明已经入事件循环状态。如果没有进入,那么继续往下走。
/// 将几个标识状态对应初始化
threadData->quitNow = false;
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();
int QEventLoop::exec(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
//we need to protect from race condition with QThread::exit
QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread.loadAcquire()))->mutex);
if (d->threadData->quitNow) // 已经设置为false
return -1;
if (d->inExec) { // 已经设置为true
qWarning("QEventLoop::exec: 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);
++d->threadData->loopLevel; /// 线程等级提升+1
d->threadData->eventLoops.push(d->q_func()); /// 事件入栈
locker.unlock();
}
~LoopReference()
{
if (exceptionCaught) {
qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
"exceptions from an event handler is not supported in Qt.\n"
"You must not let any exception whatsoever propagate through Qt code.\n"
"If that is not possible, in Qt 5 you must at least reimplement\n"
"QCoreApplication::notify() and catch all exceptions there.\n");
}
locker.relock();
QEventLoop *eventLoop = d->threadData->eventLoops.pop();
Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
Q_UNUSED(eventLoop); // --release warning
d->inExec = false;
--d->threadData->loopLevel;
}
};
LoopReference ref(d, locker);
// remove posted quit events when entering a new event loop
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 (d->threadData->loopLevel > 1)
emscripten_sleep(1);
#endif
/**************************************************************************/
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec); /// 加入到事件循环stack中
/**************************************************************************/
ref.exceptionCaught = false;
return d->returnCode.loadRelaxed();
}