在我早期开发的时候,处理循环问题就喜欢用for直接运行到底, 但如果要同时更新界面的输出信息、进度信息,不幸遇到几万次甚至几十万次循环的时候,随便动一下界面就白屏了,界面上其他的操作就完全没法响应。
举个小例子:
void MainWindow::on_btn_print_clicked()
{
//清理输出信息
ui->textBrowser->clear();
//获取打印行数
int count = ui->spinBox->value();
//设置进度值范围
ui->progressBar->setRange(0, count);
ui->progressBar->setValue(0);
//循环打印
for(int i = 0; i < count; i++) {
ui->textBrowser->append("最近多次持续40℃以上高温天气,未来一周左右还会继续...");
//将数据输出到文件中或是做一些其他操作...
//更新进度值
ui->progressBar->setValue(i+1);
}
ui->textBrowser->append("输出完毕...");
}
我们来运行一下demo,看一下效果,这里是GIF截图:
如图所示,随便点一下就卡住了。后来想到用线程处理数据上的操作, 但是UI的打印输出只能在主线程中进行 ,循环太久,主线程还是会卡顿,毕竟CPU不会给一个APP太多的占用时间。后来在别人的开源代码中学到了一个方法,只需要一句话就解决了这个问题。
//循环打印
for(int i = 0; i < count; i++) {
ui->textBrowser->append("最近多次持续40℃以上高温天气,未来一周左右还会继续...");
//将数据输出到文件中或是做一些其他操作...
//更新进度值
ui->progressBar->setValue(i+1);
//优先响应UI事件,防止卡死, 提升用户体验
qApp->processEvents();
}
就是这个 qApp->processEvents(), 以下是Qt帮助文档中的描述:
[static] void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents)
Processes all pending events for the calling thread according to the specified flags until there are no more events to process.
You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
In the event that you are running a local loop which calls this function continuously, without an event loop, the DeferredDelete events will not be processed. This can affect the behaviour of widgets, e.g. QToolTip, that rely on DeferredDelete events to function properly. An alternative would be to call sendPostedEvents() from within that local loop.
Calling this function processes events only for the calling thread.
Note: This function is thread-safe.
根据指定的标志处理调用线程的所有挂起事件,直到没有更多事件要处理。当您的程序忙于执行长时间操作(例如复制文件)时,您可以偶尔调用此函数。如果您正在运行一个连续调用此函数的本地循环,而没有事件循环,则不会处理 DeferredDelete 事件。这会影响widgets的行为,例如QToolTip,依赖 DeferredDelete 事件才能正常运行。另一种方法是从该本地循环中调用 sendPostedEvents()。调用此函数仅为调用线程处理事件。
优化后, 我们再来看一下运行结果:
打印50W行数据的同时,执行其他操作不会卡住了。
以下是完整的代码:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_btn_print_clicked();
void on_btn_stop_clicked();
private:
Ui::MainWindow *ui;
bool print_lock;
bool stop_flag;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
print_lock = false;
stop_flag = false;
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_btn_print_clicked()
{
if (print_lock){
return;
}
//防止重复执行
print_lock = true;
//获取打印行数
int count = ui->spinBox->value();
//清理输出信息
ui->textBrowser->clear();
//设置进度值范围
ui->progressBar->setRange(0, count);
ui->progressBar->setValue(0);
//循环打印
for(int i = 0; i < count; i++) {
ui->textBrowser->append("最近多次持续40℃以上高温天气,未来一周左右还会继续...");
//将数据输出到文件中或是做一些其他操作...
//更新进度值
ui->progressBar->setValue(i+1);
//停止
if (stop_flag) {
break;
}
//优先响应UI事件,防止卡死, 提升用户体验
qApp->processEvents();
}
if (stop_flag){
ui->textBrowser->append("输出中断...");
} else {
ui->textBrowser->append("输出完毕...");
}
print_lock = false;
stop_flag = false;
}
void MainWindow::on_btn_stop_clicked()
{
//正在打印中
if (print_lock)
stop_flag = true;
else
stop_flag = false;
}