Skip to content

Commit f3a91a6

Browse files
committed
修改第四章的“异步任务执行”措辞以及分段
1 parent 943b019 commit f3a91a6

File tree

1 file changed

+22
-9
lines changed

1 file changed

+22
-9
lines changed

md/04同步操作.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -972,9 +972,11 @@ bool wait_loop(){
972972
973973
## 异步任务执行
974974
975-
假设有一系列耗时任务需要完成,我们会使用异步多线程执行这些任务,从而减轻主线程的计算压力。比如:一些带 UI 程序,我们不能阻塞 UI 线程(不然可能造成界面卡顿),耗时任务应该是异步创建线程执行的
975+
在开发带有 UI 的程序时,主线程用于处理 UI 更新和用户交互,如果在主线程中执行耗时任务会导致界面卡顿。因此,需要使用异步任务来减轻主线程的压力。以下是一个使用 Qt 实现异步任务的示例,展示了如何在不阻塞 UI 线程的情况下执行耗时任务,并更新进度条
976976
977-
比如,一个**进度条**,我们以 Qt 为例:
977+
### 背景介绍
978+
979+
在 Qt 中,GUI 控件通常只能在创建它们的线程中进行操作,因为它们是线程不安全的。我们可以使用 `QMetaObject::invokeMethod` 来跨线程调用主线程上的控件方法,从而在其他线程中安全地更新 UI 控件。以下代码示例展示了如何通过 `QMetaObject::invokeMethod` 确保 UI 控件的更新操作在主线程中执行。
978980
979981
```cpp
980982
void task(){
@@ -998,15 +1000,17 @@ void task(){
9981000
}
9991001
```
10001002

1001-
> Qt 中,GUI 控件通常只能在创建它们的线程中进行操作,是因为 GUI 控件是线程不安全的。而 `QMetaObject::invokeMethod` 函数是 Qt 中的一种跨线程通信机制,它允许我们在不同的线程之间安全地发送信号和调用槽函数。也就是说我们传递的 lambda 是在**主线程运行的**。
1002-
1003-
很显然,我们创建了一个异步任务,指明执行策略,它在线程中执行。如果不这样做,将会**卡界面**(你可以把这个函数的第一行与最后一行注释掉)。
1003+
上面的代码创建了一个异步任务,并指明了执行策略。任务在线程中执行,不会阻塞 UI 线程。如果不这样做,界面将会卡顿(可以尝试将函数的第一行与最后一行注释掉以验证这一点)。
10041004

10051005
![进度条](../image/第四章/进度条.png)
10061006

1007-
在启动进度条后,能够正常点击“**测试**”按钮然后触发弹窗,就是没有问题,你也可以尝试不创建线程,那么界面将会卡主,你点击不了“**测试**”按钮,也移动不了这个窗口。
1007+
在启动进度条后,能够正常点击“**测试**”按钮并触发弹窗,说明 UI 没有被阻塞。相反,如果不使用线程,界面将会卡住,无法点击“**测试**”按钮或移动窗口。
1008+
1009+
### 项目说明
1010+
1011+
项目使用 Visual Studio 编写,可以直接安装 Qt 插件后打开。项目结构简单,所有界面与设置均通过代码控制,无需进行其他 UI 操作。重点关注 `async_progress_bar.h``async_progress_bar.cpp``main.cpp` 这三个文件,它们位于仓库的 **`code`** 文件夹中。
10081012

1009-
我们的项目是使用 visual studio 编写的,你可以直接安装 Qt 插件后使用它打开。此项目很小,为了简洁,所有的的界面与设置均是代码控制,而没有进行别的 ui 操作,也就是说,你只需要注意 `async_progress_bar.h`、`async_progress_bar.cpp`、`main.cpp` 这三个文件即可。它们存放在仓库以及目录 **`code`** 文件夹中。
1013+
### 完整代码实现
10101014

10111015
```cpp
10121016
class async_progress_bar : public QMainWindow{
@@ -1099,8 +1103,17 @@ async_progress_bar::async_progress_bar(QWidget *parent)
10991103
}
11001104
```
11011105
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+
### 实践建议
11031116
11041117
这个例子其实很好的展示了多线程异步的作用,因为有 UI,所以很直观,毕竟如果你不用线程,那么不就卡界面了,用了就没事。
11051118
1106-
建议打开此项目自己编译运行,并尝试修改,如有必要,建议自己也写一遍,代码较为简单,就不再介绍了
1119+
建议下载并运行此项目,通过实际操作理解代码效果。同时,可以尝试修改代码,观察不同情况下 UI 的响应情况,以加深对异步任务处理的理解

0 commit comments

Comments
 (0)