@@ -972,9 +972,11 @@ bool wait_loop(){
972
972
973
973
## 异步任务执行
974
974
975
- 假设有一系列耗时任务需要完成,我们会使用异步多线程执行这些任务,从而减轻主线程的计算压力。比如:一些带 UI 程序,我们不能阻塞 UI 线程(不然可能造成界面卡顿),耗时任务应该是异步创建线程执行的 。
975
+ 在开发带有 UI 的程序时,主线程用于处理 UI 更新和用户交互,如果在主线程中执行耗时任务会导致界面卡顿。因此,需要使用异步任务来减轻主线程的压力。以下是一个使用 Qt 实现异步任务的示例,展示了如何在不阻塞 UI 线程的情况下执行耗时任务,并更新进度条 。
976
976
977
- 比如,一个**进度条**,我们以 Qt 为例:
977
+ ### 背景介绍
978
+
979
+ 在 Qt 中,GUI 控件通常只能在创建它们的线程中进行操作,因为它们是线程不安全的。我们可以使用 `QMetaObject::invokeMethod` 来跨线程调用主线程上的控件方法,从而在其他线程中安全地更新 UI 控件。以下代码示例展示了如何通过 `QMetaObject::invokeMethod` 确保 UI 控件的更新操作在主线程中执行。
978
980
979
981
```cpp
980
982
void task(){
@@ -998,15 +1000,17 @@ void task(){
998
1000
}
999
1001
```
1000
1002
1001
- > Qt 中,GUI 控件通常只能在创建它们的线程中进行操作,是因为 GUI 控件是线程不安全的。而 `QMetaObject::invokeMethod` 函数是 Qt 中的一种跨线程通信机制,它允许我们在不同的线程之间安全地发送信号和调用槽函数。也就是说我们传递的 lambda 是在**主线程运行的**。
1002
-
1003
- 很显然,我们创建了一个异步任务,指明执行策略,它在线程中执行。如果不这样做,将会**卡界面**(你可以把这个函数的第一行与最后一行注释掉)。
1003
+ 上面的代码创建了一个异步任务,并指明了执行策略。任务在线程中执行,不会阻塞 UI 线程。如果不这样做,界面将会卡顿(可以尝试将函数的第一行与最后一行注释掉以验证这一点)。
1004
1004
1005
1005
![ 进度条] ( ../image/第四章/进度条.png )
1006
1006
1007
- 在启动进度条后,能够正常点击“**测试**”按钮然后触发弹窗,就是没有问题,你也可以尝试不创建线程,那么界面将会卡主,你点击不了“**测试**”按钮,也移动不了这个窗口。
1007
+ 在启动进度条后,能够正常点击“** 测试** ”按钮并触发弹窗,说明 UI 没有被阻塞。相反,如果不使用线程,界面将会卡住,无法点击“** 测试** ”按钮或移动窗口。
1008
+
1009
+ ### 项目说明
1010
+
1011
+ 项目使用 Visual Studio 编写,可以直接安装 Qt 插件后打开。项目结构简单,所有界面与设置均通过代码控制,无需进行其他 UI 操作。重点关注 ` async_progress_bar.h ` 、` async_progress_bar.cpp ` 和 ` main.cpp ` 这三个文件,它们位于仓库的 ** ` code ` ** 文件夹中。
1008
1012
1009
- 我们的项目是使用 visual studio 编写的,你可以直接安装 Qt 插件后使用它打开。此项目很小,为了简洁,所有的的界面与设置均是代码控制,而没有进行别的 ui 操作,也就是说,你只需要注意 `async_progress_bar.h`、`async_progress_bar.cpp`、`main.cpp` 这三个文件即可。它们存放在仓库以及目录 **`code`** 文件夹中。
1013
+ ### 完整代码实现
1010
1014
1011
1015
``` cpp
1012
1016
class async_progress_bar : public QMainWindow {
@@ -1099,8 +1103,17 @@ async_progress_bar::async_progress_bar(QWidget *parent)
1099
1103
}
1100
1104
```
1101
1105
1102
- 为了展示 `QMetaObject::invokeMethod` 中的 lambda 是在主线程运行,我们打印了线程 ID,因为 C++11 的 `std::this_thread::get_id()` 返回的那个内部类型没办法直接转换为 `unsigned int`,我们就直接使用了 win32 的 API *`_Thrd_id()`* 了。如果您是 Linux 之类的环境,使用 [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) 接口 [*`pthread_self()`*](https://pubs.opengroup.org/onlinepubs/009696699/functions/pthread_self.html)。
1106
+ ### 注意事项
1107
+
1108
+ - `QMetaObject::invokeMethod` 的 lambda 是在主线程运行的,通过显示的线程 ID 可以验证这一点。
1109
+ - 使用 `std::async` 的 `std::launch::async` 参数强制异步执行任务,以确保任务在新线程中运行。
1110
+
1111
+ ### 跨平台兼容性
1112
+
1113
+ C++11 的 `std::this_thread::get_id()` 返回的内部类型没办法直接转换为 `unsigned int`,我们就直接使用了 win32 的 API *`_Thrd_id()`* 了。如果您是 Linux 之类的环境,使用 [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) 接口 [*`pthread_self()`*](https://pubs.opengroup.org/onlinepubs/009696699/functions/pthread_self.html)。
1114
+
1115
+ ### 实践建议
1103
1116
1104
1117
这个例子其实很好的展示了多线程异步的作用,因为有 UI,所以很直观,毕竟如果你不用线程,那么不就卡界面了,用了就没事。
1105
1118
1106
- 建议打开此项目自己编译运行,并尝试修改,如有必要,建议自己也写一遍,代码较为简单,就不再介绍了 。
1119
+ 建议下载并运行此项目,通过实际操作理解代码效果。同时,可以尝试修改代码,观察不同情况下 UI 的响应情况,以加深对异步任务处理的理解 。
0 commit comments