0%

qt_8_消息循环机制

简介

  • Qt消息循环机制相关学习笔记

深入了解Qt消息循环及线程相关性

什么是Qt消息循环

  • Qt消息循环,就是从一个队列中不断取出消息,并响应消息的过程。窗体的鼠标,键盘,输入法,绘制,各种消息,都来自于Qt的消息循环。

  • 以Windows操作系统为例,Qt接管Windows原生窗口消息,并翻译成Qt的消息,派发给程序下的各个子对象,子QWidget等,通过接管层,可以很好屏蔽不同平台之间的差异性,开发人员不需要关心Windows或者X11的消息的差异性,只需要搞清楚各个QEvent之间是什么含义。

  • 最开始的Qt消息循环开始于 QCoreApplication::exec。用户创建出一个QCoreApplication,或者说更多情况下是QApplication,执行QCoreApplication::exec,一个应用程序便开始了。QCoreApplication会不断从操作系统获取消息,并且分发给QObject。

  • 如果没有消息循环,那么Qt的信号和槽无法完全使用。有些函数也无法正确执行。举个例子,通过QueuedConnection连接的信号,其实是将一个事件压入了消息循环,如果没有QCoreApplication::exec,那么这个消息循环将永远无法派发到指定的对象。

什么是线程相关性

  • 准确来说,应该是指QObject的线程相关性。以Qt文档中的示意图来做说明

  • 当我们创建一个QObject时,它会与创建自己所在的线程绑定,它参与的消息循环,其实是它所在线程的消息循环。加入某个线程没有默认的QThread::exec,那么该线程上的QObject则无法接收到事件。另外,如果两个不同线程的QObject需要相互通信,那么只能通过QueuedConnection的方式,异步通知对象线程,在下一轮消息循环处理QObject的消息。

  • QObject的线程相关性默认和它的parent保持一致。如果一个QObject没有parent,那么可以通过 moveToThread,将它的线程相关性切换到指定线程

  • 了解QObject的线程相关性非常重要,很多初学者常常分不清一个多线程中哪些QObject应该由主线程创建,哪些应该由工作线程创建。我的观点是,它参与哪个消息循环,就由哪个来创建。

  • 正因为这样的特性,我们才可以理解什么叫做AutoConnection。通过AutoConnection连接的两个QObject,如果是在同一个线程,那么可以直接调用DirectConnection,如果不是在同一个线程,那么就通过事件通知的方式QueueConnection来调用。通过信号和槽,事件或者QueuedConnection方式来进行线程间的通讯,尤其是与UI线程通讯,永远是最优雅的方式之一。

什么是消息循环

  • 关于消息循环的过程。
  • 首先,用户通过GetMessage,PeekMessage等函数,从消息队列中取出事件,接下来,通过DispatchMessage来分发事件。系统将这个事件分发到对应的窗口处理函数WNDPROC中进行处理
  • 在绝大部分GUI程序中,GetMessage,DispatchMessage是写在一个死循环中的,除非程序退出,否则会一直处理各种事件。

消息队列的相关性

Qt消息循环的基础:窗体事件

  • 在Windows中,要处理事件一定要有一个窗体,在Qt中,事件一共有两类,一类是和窗体无关的实践,例如QTimerEvent,另外一类就是常见的窗体事件,例如鼠标,键盘,绘制等事件。因此,qt至少有两个WNDPROC,一个处理Timer等事件,一个处理QWidget中的事件。
  • 刚刚也提到,Windows事件其实是和线程相关的,那么也就是说,对于每一个QObject的对象,它必须要有自己所在线程的信息。不同线程的对象是无法直接通信的,要通过事件才可以。
  • 在Qt中,消息循环在 QEventLoop 类中实现。通过QEventLoop::exec可以进入一个消息循环的阻塞状态中,也就是不断地PeekMessage-DispatchMessage。其实,QEventLoop里面几乎没有实现任何细节。
  • 不难想到,QEventLoop通过内部的一层抽象,来不断从系统获取和处理消息,而这一层抽象,是和线程相关的。所有相同的线程,完全可以共用这层抽象。

qt 消息循环机制

在 Qt 中,消息循环机制是事件驱动编程的一部分,负责管理和调度事件的处理。在 Qt 应用程序中,消息循环是确保程序能够响应用户输入、窗口更新、定时器等事件的核心机制。理解 Qt 的消息循环机制对于开发交互式应用程序至关重要。

Qt 消息循环机制概述

Qt 的事件处理机制基于 事件队列,当事件发生时(例如用户点击按钮、输入文本、定时器超时等),事件被放入事件队列,然后通过事件循环逐一处理。事件循环会持续运行,直到应用程序退出。

事件和消息

在 Qt 中,“事件”和“消息”通常是同义词,它们代表着某种需要处理的动作。常见的事件类型包括:

  • 用户输入事件:如 QKeyEvent(键盘输入)、QMouseEvent(鼠标点击或移动)
  • 窗口事件:如 QResizeEvent(窗口大小调整)、QCloseEvent(窗口关闭事件)
  • 定时器事件:如 QTimerEvent(定时器超时事件)
  • 自定义事件:如通过 QCoreApplication::postEvent() 创建和发送的事件

消息循环的基本工作原理

Qt 的消息循环机制通过 QCoreApplication::exec() 函数启动。当调用 exec() 时,它会进入一个循环,等待并处理事件。这是消息循环的核心,它会处理所有传入的事件,直到程序结束或调用 exit() 函数。

步骤:

  1. 事件触发:当用户与程序交互时(如点击按钮、输入键盘、定时器触发等),相应的事件会被发送到事件队列。
  2. 事件入队:事件会被放入一个队列中,等待处理。
  3. 事件循环QCoreApplication::exec() 会不断地从队列中取出事件并分发到合适的对象(通常是控件或窗口),然后调用相应的事件处理函数(如 mousePressEvent()keyPressEvent() 等)。
  4. 事件分发:当一个事件被取出后,Qt 会根据事件类型调用相应的处理函数。
  5. 退出循环:当消息循环完成(例如用户请求退出程序或调用 exit()),消息循环结束,程序退出。

消息循环的代码示例

下面是一个典型的 Qt 程序结构,展示了消息循环的使用。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[]) {
QApplication app(argc, argv); // 初始化应用程序,启动事件循环

QPushButton button("点击我");
button.resize(200, 100);
button.show();

return app.exec(); // 启动事件循环,等待并处理事件
}

在这个简单的例子中:

  1. QApplication app(argc, argv); 会初始化应用程序。
  2. button.show(); 显示按钮,等待用户交互。
  3. app.exec(); 启动消息循环并等待处理用户事件(例如鼠标点击、键盘输入等)。

当用户点击按钮时,Qt 会触发 QPushButton 的相关事件,如 mousePressEvent(),并调用相应的事件处理方法。

Qt 事件循环的工作方式

1. 事件队列

每个应用程序都有一个事件队列,用于存储需要处理的事件。事件队列是由操作系统和 Qt 内部管理的。当事件被触发时,它们会被放入队列。

2. 事件处理

事件的处理通常是通过事件重载来实现的。例如,在自定义窗口类中,你可以重载 mousePressEvent() 来处理鼠标点击事件。

1
2
3
void MyWidget::mousePressEvent(QMouseEvent *event) {
qDebug() << "鼠标按下:位置 (" << event->x() << "," << event->y() << ")";
}

3. 事件循环的执行

Qt 的事件循环会从事件队列中提取事件,并根据事件类型调用合适的事件处理函数。例如:

  • 如果事件是键盘输入,则会调用 keyPressEvent()
  • 如果事件是鼠标点击,则会调用 mousePressEvent()

4. 事件的优先级

Qt 处理事件的顺序是由事件的类型和优先级决定的。通常,事件会按照到达顺序进行处理,但也可以通过改变事件的优先级来控制事件的处理顺序。

自定义事件和事件发送

你可以在 Qt 中发送自定义事件,甚至通过 QCoreApplication::postEvent() 来在事件队列中插入事件。自定义事件通常用于在不同对象之间传递信息。

示例:自定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <QApplication>
#include <QWidget>
#include <QEvent>
#include <QDebug>

class MyEvent : public QEvent {
public:
MyEvent() : QEvent(QEvent::User) {} // 设置自定义事件类型
};

class MyWidget : public QWidget {
protected:
void customEvent(QEvent *event) override {
if (event->type() == QEvent::User) {
qDebug() << "处理自定义事件";
}
}
};

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

MyWidget widget;
widget.show();

QCoreApplication::postEvent(&widget, new MyEvent()); // 发送自定义事件

return app.exec(); // 启动事件循环
}

在这个示例中,MyEvent 是一个自定义事件,继承自 QEvent。在 MyWidget 中重载了 customEvent() 来处理这个事件。

线程与消息循环

Qt 的事件循环通常与主线程相关。主线程是 Qt 应用程序的 GUI 线程,所有的界面更新和事件处理都在主线程中执行。如果你在其他线程中执行操作,必须确保线程安全。例如,可以使用 QEventLoopQTimer 在非主线程中实现事件循环。


总结

  • 消息循环是事件驱动编程的核心,确保 Qt 应用程序能够响应各种事件。
  • 事件通过 QCoreApplication::exec() 启动并被依次处理。
  • Qt 会自动管理事件队列,触发事件时,事件会被推入队列,消息循环会逐一取出并调用相应的事件处理函数。
  • 通过 QEventQCoreApplication::postEvent(),可以发送自定义事件,实现对象间的通信。

Qt 的消息循环机制是事件驱动模型的一个典型实现,理解这一机制对于构建响应式、交互式的 GUI 应用程序至关重要。

感谢老板支持!敬礼(^^ゞ