借助QtConcurrent
和QApplication::processEvents()
简化处理耗时任务以防界面假死。
问题
有如下需求
点击开始
按钮后,执行耗时操作,该按钮进行中
后的.
的数量随时间变化


代码如下
mainwindow.h
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 30 31 32 33 34 35 36 37 38
| #ifndef MAINWINDOW_H #define MAINWINDOW_H
#include <QMainWindow> #include <QCloseEvent>
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow { Q_OBJECT
public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow();
private: void doSomeThing0(); void toDoOneThing(int _index);
private: Ui::MainWindow *ui;
const std::vector<QString> DOT_VECTOR = { ".", "..", "...", "....", ".....", };
const int SLEEP_TIME = 20; };
#endif
|
mainwindow.cpp
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include "mainwindow.h" #include "ui_mainwindow.h" #include <QtConcurrent> #include <QThread>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->pushButton, &QPushButton::clicked, [=]() { ui->pushButton->setEnabled(false);
doSomeThing0();
ui->pushButton->setText(u8"开始"); ui->pushButton->setEnabled(true); }); }
MainWindow::~MainWindow() { delete ui; }
void MainWindow::doSomeThing0() { for (size_t i = 0; i < 10000; i++) { toDoOneThing(i); } }
void MainWindow::toDoOneThing(int _index) { QString buttonText = u8"进行中"; buttonText += DOT_VECTOR[_index / 20 % DOT_VECTOR.size()]; ui->pushButton->setText(buttonText);
QThread::msleep(SLEEP_TIME); }
|
以上代码运行后点击开始
,界面假死。
很多情况下,我们要在界面种处理某些耗时的操作,如果直接调用的话,主界面就会卡死,一般来说通过多线程可以解决该问题,包括最传统的run
()和新式的QObject::moveToThread()
具体可参考该文章Qt 多线程编程之敲开 QThread 类的大门 - 知乎 (zhihu.com)
有时候耗时操作逻辑很简单,代码也没有几行,单独封装一个类有点麻烦,有没有更省事的方法呢?
单线程方式
可以单线程借助QApplication::processEvents()
实现以上需求:
mainwindow.h
1 2 3 4 5 6
| private: void doSomeThing0(); void doSomeThing1(); void toDoOneThing(int _index);
|
mainwindow.cpp
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 30 31 32 33 34 35 36 37
| #include "mainwindow.h" #include "ui_mainwindow.h" #include <QtConcurrent> #include <QThread>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->pushButton, &QPushButton::clicked, [=]() { ui->pushButton->setEnabled(false);
doSomeThing1();
ui->pushButton->setText(u8"开始"); ui->pushButton->setEnabled(true); }); }
void MainWindow::doSomeThing0() { for (size_t i = 0; i < 10000; i++) { toDoOneThing(i); } }
void MainWindow::doSomeThing1() { for (size_t i = 0; i < 10000; i++) { toDoOneThing(i); QApplication::processEvents(); } }
|
可以看出doSomeThing1()
比doSomeThing0()
只多一行代码。
多线程方式
这里使用QtConcurrent::run()
函数,实现起来相当简洁:
.pro
mainwindow.h
1 2 3 4 5
| private: void doSomeThing0(); void doSomeThing2(); void toDoOneThing(int _index);
|
mainwindow.cpp
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 30 31 32 33 34 35 36 37 38 39
| #include "mainwindow.h" #include "ui_mainwindow.h" #include <QtConcurrent> #include <QThread>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->pushButton, &QPushButton::clicked, [=]() { ui->pushButton->setEnabled(false);
doSomeThing2();
ui->pushButton->setText(u8"开始"); ui->pushButton->setEnabled(true); }); }
void MainWindow::doSomeThing0() { for (size_t i = 0; i < 10000; i++) { toDoOneThing(i); } }
void MainWindow::doSomeThing2() { auto future = QtConcurrent::run(this, &MainWindow::doSomeThing0);
while (future.isFinished() == false) { QApplication::processEvents(); QThread::msleep(20); } }
|
Concurrent是并发的意思,QtConcurrent是一个命名空间,提供了一些高级的 API,使得在编写多线程的时候,无需使用低级线程原语,如读写锁,等待条件或信号。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。Qt QtConcurrent之 Run 函数用法_luoyayun361的专栏-CSDN博客_qtconcurrent
QtConcurrent::run()
从第三个参数开始为所调用函数的参数。
小问题
以上代码执行时会出现点击关闭按钮无法退出的问题,解决方法如下:
mainwindow.h
1 2 3 4 5 6
| protected: void closeEvent(QCloseEvent *event);
private: bool isFinished_ = false;
|
mainwindow.cpp
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
| void MainWindow::closeEvent(QCloseEvent * event) { isFinished_ = true; return QMainWindow::closeEvent(event); }
void MainWindow::doSomeThing0() { for (size_t i = 0; i < 10000; i++) { if (isFinished_ == true) break; toDoOneThing(i); } }
void MainWindow::doSomeThing1() { for (size_t i = 0; i < 10000; i++) { if (isFinished_ == true) break; toDoOneThing(i);
QApplication::processEvents(); } }
|
示例代码路径
Backgrounder · yuque/demo - 码云 - 开源中国 (gitee.com)