简介
- 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是写在一个死循环中的,除非程序退出,否则会一直处理各种事件。
消息队列的相关性
参考链接:
系统将用创建某Window的线程来分发消息。例如窗体1在线程A创建,窗体2在线程B创建,那么它们的WNDPROC则是由不同线程来回调的。一般的,我们也只会在主线程中创建窗体,不过系统还是允许在各个线程中处理窗口的。
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()
函数。
步骤:
- 事件触发:当用户与程序交互时(如点击按钮、输入键盘、定时器触发等),相应的事件会被发送到事件队列。
- 事件入队:事件会被放入一个队列中,等待处理。
- 事件循环:
QCoreApplication::exec()
会不断地从队列中取出事件并分发到合适的对象(通常是控件或窗口),然后调用相应的事件处理函数(如mousePressEvent()
、keyPressEvent()
等)。 - 事件分发:当一个事件被取出后,Qt 会根据事件类型调用相应的处理函数。
- 退出循环:当消息循环完成(例如用户请求退出程序或调用
exit()
),消息循环结束,程序退出。
消息循环的代码示例
下面是一个典型的 Qt 程序结构,展示了消息循环的使用。
例子:
1 |
|
在这个简单的例子中:
QApplication app(argc, argv);
会初始化应用程序。button.show();
显示按钮,等待用户交互。app.exec();
启动消息循环并等待处理用户事件(例如鼠标点击、键盘输入等)。
当用户点击按钮时,Qt 会触发 QPushButton
的相关事件,如 mousePressEvent()
,并调用相应的事件处理方法。
Qt 事件循环的工作方式
1. 事件队列
每个应用程序都有一个事件队列,用于存储需要处理的事件。事件队列是由操作系统和 Qt 内部管理的。当事件被触发时,它们会被放入队列。
2. 事件处理
事件的处理通常是通过事件重载来实现的。例如,在自定义窗口类中,你可以重载 mousePressEvent()
来处理鼠标点击事件。
1 | void MyWidget::mousePressEvent(QMouseEvent *event) { |
3. 事件循环的执行
Qt 的事件循环会从事件队列中提取事件,并根据事件类型调用合适的事件处理函数。例如:
- 如果事件是键盘输入,则会调用
keyPressEvent()
。 - 如果事件是鼠标点击,则会调用
mousePressEvent()
。
4. 事件的优先级
Qt 处理事件的顺序是由事件的类型和优先级决定的。通常,事件会按照到达顺序进行处理,但也可以通过改变事件的优先级来控制事件的处理顺序。
自定义事件和事件发送
你可以在 Qt 中发送自定义事件,甚至通过 QCoreApplication::postEvent()
来在事件队列中插入事件。自定义事件通常用于在不同对象之间传递信息。
示例:自定义事件
1 |
|
在这个示例中,MyEvent
是一个自定义事件,继承自 QEvent
。在 MyWidget
中重载了 customEvent()
来处理这个事件。
线程与消息循环
Qt 的事件循环通常与主线程相关。主线程是 Qt 应用程序的 GUI 线程,所有的界面更新和事件处理都在主线程中执行。如果你在其他线程中执行操作,必须确保线程安全。例如,可以使用 QEventLoop
或 QTimer
在非主线程中实现事件循环。
总结
- 消息循环是事件驱动编程的核心,确保 Qt 应用程序能够响应各种事件。
- 事件通过
QCoreApplication::exec()
启动并被依次处理。 - Qt 会自动管理事件队列,触发事件时,事件会被推入队列,消息循环会逐一取出并调用相应的事件处理函数。
- 通过
QEvent
和QCoreApplication::postEvent()
,可以发送自定义事件,实现对象间的通信。
Qt 的消息循环机制是事件驱动模型的一个典型实现,理解这一机制对于构建响应式、交互式的 GUI 应用程序至关重要。