基于Qt5.14.2和mingw的Qt源码学习(五) — 事件循环之windows事件循环基础和控制台中的事件循环
前面介绍了反射相关的东西,我们提到了Qt的事件循环。其实这种事件循环的机制在很多编程语言和系统中都有使用。下面我们一起看下Qt实现事件循环相关的类和函数吧。
一、QEvent
这是Qt所有事件种类的基类。那么我们把一部分声明列在下面。
class Q_CORE_EXPORT QEvent // event base class
{
Q_GADGET
QDOC_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
public:
enum Type {
...
};
Q_ENUM(Type)
explicit QEvent(Type type);
inline bool spontaneous() const {
return spont;
inline void accept() {
m_accept = true; }
inline void ignore() {
m_accept = false; }
static int registerEventType(int hint = -1) noexcept;
};
上来的第一个Q_GADGET我们上次简单说过。这次我们展开看看里面是些什么东西。
1、Q_GADGET
#define Q_GADGET \
public: \
static const QMetaObject staticMetaObject; \
void qt_check_for_QGADGET_macro(); \
typedef void QtGadgetHelper; \
private: \
QT_WARNING_PUSH \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
QT_WARNING_POP \
QT_ANNOTATE_CLASS(qt_qgadget, "") \
/*end*/
这里明显比Q_OBJECT对应的内容少了不少。不支持翻译文件,同时少了几个方法的声明。但是由于它保留了 staticMetaObject 静态对象以及 qt_static_metacall,我们还是可以调用Qt属性、方法和枚举。
2、spontaneous()
返回事件来源,是外部定义的还是内部定义的。那么我们看构造函数的话知道每种事件都被定义一个id。那么Qt是如何保证内部事件ID和外部事件ID的独立性呢?todo
3、accept() 和 ignore()
这是给类对象中的accept变量赋值,表示这个event的状态。具体这个变量的用法咱们后面再说。
二、QEventLoop
这个看名字就知道是Qt里用于事件循环处理的类。
class Q_CORE_EXPORT QEventLoop : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QEventLoop)
public:
explicit QEventLoop(QObject *parent = nullptr);
~QEventLoop();
enum ProcessEventsFlag {
AllEvents = 0x00,
ExcludeUserInputEvents = 0x01,
ExcludeSocketNotifiers = 0x02,
WaitForMoreEvents = 0x04,
X11ExcludeTimers = 0x08,
EventLoopExec = 0x20,
DialogExec = 0x40
};
Q_DECLARE_FLAGS(ProcessEventsFlags, ProcessEventsFlag)
bool processEvents(ProcessEventsFlags flags = AllEvents);
void processEvents(ProcessEventsFlags flags, int maximumTime);
int exec(ProcessEventsFlags flags = AllEvents);
void exit(int returnCode = 0);
bool isRunning() const;
void wakeUp();
bool event(QEvent *event) override;
public Q_SLOTS:
void quit();
};
1、ProcessEventsFlags
先了解事件的处理标志:
QEventLoop::AllEvents — 处理所有事件
QEventLoop::WaitForMoreEvents — 如果没有待处理的消息,则继续等待下条消息进来。
下面这两种标志是对特定消息的忽略。那么要注意这种忽略并不会丢掉消息。而是在下次没有这种标志的情况下对消息再处理:
QEventLoop::ExcludeUserInputEvents — 不处理用户输入事件(即不处理用户交互如鼠标、键盘输入的消息)
QEventLoop::ExcludeSocketNotifiers — 不处理socket消息
前面四种标志对应的事件处理都是一般性,针对所有系统的。后面这三个和特定的系统相关:
QEventLoop::X11ExcludeTimers — 在x11系统中不处理定时器消息。
QEventLoop::EventLoopExec — 没太理解
QEventLoop::DialogExec — 貌似和手机系统有关
2、exec
这个函数有一个参数就是上面说的标志,如果传了的话只处理特定类型的消息。Qt手册上也说了如果不调用exec,所有用户交互都无法实现,除了模态对话框的事件循环。
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)
return -1;
if (d->inExec) {
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;
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);
ref.exceptionCaught = false;
return d->returnCode.loadRelaxed();
}
前面两个逻辑判断很简单。一个是用于线程中判断当前线程是否已结束或已停止事件循环;第二个是用于当前事件循环正在执行则不进行事件循环。那么这里exec执行的开始其实是随着 struct LoopReference 实例的创建,我们先学习下这个结构体。
(1)QAtomicInteger
前面我们说过这个是原子类型。原子类型的内存模型可以参考GCC 原子操作中 Acquire/Release/Consume/Relaxed 内存模型。其实无论我们这里无论调用哪个函数,操作都是原子类型的,只是不同的函数编译器的优化方式不一样。我们看看 QAtomicInteger 的一些基本操作。
a. loadAcquire storeRelease
loadAcquire 是获取变量值。它确保在它后面相关变量的语句一定在它后面执行,但是不保证前面的语句一定在前面执行。
storeRelease 是写入变量值。它确保在它前面相关变量的语句一定在它前面执行,但是不保证后面的语句一定在后面执行。
写个简单的例子:
#include <QCoreApplication>
#include <QThread>
#include <QThread>
#include <QAtomicInteger>
#include <QDebug>
typedef void(*myThreadFunc)();
static QAtomicInteger<int> g_atomicInt(-1);
static int g_iTest = -1;
const int CONST_ATOMIC_VALUE = 2;
const int CONST_INT_VALUE = 3;
void readValue()
{
while(g_atomicInt.loadAcquire() != CONST_ATOMIC_VALUE)
{
qDebug() << "g_atomicInt.loadAcquire() != CONST_ATOMIC_VALUE";
QThread::msleep(100);
}
qDebug() << g_iTest;
}
void writeValue()
{
QThread::msleep(1000);
g_iTest = CONST_INT_VALUE;
g_atomicInt.storeRelease(CONST_ATOMIC_VALUE);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread *threadWrite = QThread::create<myThreadFunc>(&writeValue);
QThread *threadRead = QThread::create<myThreadFunc>(&readValue);
threadRead->start();
threadWrite->start();
return a.exec();
}
这两个函数的使用可以保证线程之间的执行顺序。
b. loadRelaxed storeRelaxed
这两个函数对编译器优化后代码的执行顺序没有特定要求,一般用于不对线程执行顺序进行控制的变量读写操作中。
(2)QThreadData *threadData
这里的 threadData 是我们前面说过的 QObjectData 的一个成员变量。那么我们看一下它的初始化:
// qobject.cpp
// QObject::QObject(QObject *parent)
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
if (parent) {
QT_TRY {
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
parent = 0;
setParent(parent);
} QT_CATCH(...) {
d->threadData->deref();
QT_RETHROW;
}
}
这里我们可以看下一个对象的所属线程的来源:当其存在父对象且父对象所属线程无效时。那么什么情况下父线程无效呢?看下源代码可以找到当 QThread 注销时会把其d指针中的 threaddata 中的线程对象置空。写个小例子验证下:
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
void run()
{
while(1)
{
QThread::msleep(1000