目录
1、问题简介
在实际开发中,我们经常会进行一些比较复杂的计算和算法实现,或者是在某些特定的情况下会实例化一些类。这些操作都是很消耗时间的,如果在此时需要进行一些UI的渲染的话,这些耗时操作就会阻塞渲染线程,导致无法达到想要的效果。
例如,我们想要在进行计算时,显示一个等待动画界面,直到计算结果出来后再关闭掉这个等待界面。但是这就会导致等待界面渲染阻塞,而当计算结果出来后又将界面关闭,带来的用户使用效果就是好像卡住了一样。
问题代码示例:
(1).h文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMovie>
#include <QList>
#include <QRandomGenerator>
#include <QDateTime>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void comIntensive();
protected:
void mousePressEvent(QMouseEvent *e) override;
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
(2).cpp文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QMovie *gif = new QMovie();
gif->setFileName(":/gif.gif");
ui->label->setMovie(gif);
// 设置QLabel的背景为透明
ui->label->setStyleSheet("background-color: transparent;");
gif->start();
ui->label->hide();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::comIntensive()
{
QList<double> res;
do{
double resInf = ((qrand() * 1.00)/(RAND_MAX * 10)) * 1000.00;
if(res.isEmpty()){
res.append(resInf);
}
else{
if(!res.contains(resInf)){
res.append(resInf);
}
}
}
while(!res.isEmpty() && res.size() <= 10000);
foreach(auto var, res) {
ui->plainTextEdit->appendHtml(QString::number(var,'f',2));
}
}
void MainWindow::mousePressEvent(QMouseEvent *e)
{
if(e->button() == Qt::LeftButton){
ui->plainTextEdit->clear();
ui->label->show();
comIntensive();
ui->label->hide();
}
}
在以上示例中,我希望生成10000个(0,100)之间的不重复的含有两位小数的double类型的浮点数,在生成期间我采用QMovie来显示一个gif动画,这样在生成期间就可以有一个等待动画界面。
但是在实际操作时,由于线程阻塞而导致没有达到想要的效果,反而给用户一种卡顿的感觉。
2、解决方案
针对以上问题,有两个方法可以解决:
①开启子线程,将QMovie动画交给子线程来处理,而主线程则主要负责进行数据的计算和产生,这样的话就能够解决线程阻塞的问题。但是这样做也有一些问题,一般而言,在qt中我们会将有关UI渲染的东西都放置在主线程内,而不是通过子线程去实现。
②开启子线程,将复杂计算内容交给子线程来处理,主线程主要进行UI的渲染操作,这样同样能够解决线程阻塞的问题,同时这样也更符合我们的生产消费设计模式。
3、解决示例
(1).h文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMovie>
#include <QList>
#include <QRandomGenerator>
#include <QDateTime>
#include <QDebug>
#include <QTimer>
#include "threadlock.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
// void comIntensive();
ThreadLock *mythread;
QMovie *gif;
protected:
void mousePressEvent(QMouseEvent *e) override;
private:
Ui::MainWindow *ui;
public slots:
void onThreadFinished();
};
#endif // MAINWINDOW_H
#ifndef THREADLOCK_H
#define THREADLOCK_H
#include <QThread>
#include <QDebug>
#include <QReadWriteLock>
enum SM_Thread{
TYPE_INFO,
TYPE_DEG,
TYPE_RES,
TYPR_WAR
};
Q_NAMESPACE
Q_ENUM_NS(SM_Thread)
class ThreadLock : public QThread
{
Q_OBJECT
public:
ThreadLock();
~ThreadLock();
QList<QString> res;
SM_Thread from = TYPR_WAR;
void setLogic(SM_Thread var);
void comIntensive();
protected:
void run() override;
private:
QReadWriteLock Lock;
};
#endif // THREADLOCK_H
(2).cpp文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
gif = new QMovie();
gif->setFileName(":/gif.gif");
ui->label->setMovie(gif);
// 设置QLabel的背景为透明
ui->label->setStyleSheet("background-color: transparent;");
gif->start();
ui->label->hide();
}
MainWindow::~MainWindow()
{
delete ui;
}
//void MainWindow::comIntensive()
//{
// QList<double> res;
// do{
// double resInf = ((qrand() * 1.00)/(RAND_MAX * 10)) * 1000.00;
// if(res.isEmpty()){
// res.append(resInf);
// }
// else{
// if(!res.contains(resInf)){
// res.append(resInf);
// }
// }
// }
// while(!res.isEmpty() && res.size() <= 10000);
// foreach(auto var, res) {
// ui->plainTextEdit->appendHtml(QString::number(var,'f',2));
// }
//}
void MainWindow::mousePressEvent(QMouseEvent *e)
{
if(e->button() == Qt::LeftButton){
ui->plainTextEdit->clear();
ui->label->show();
mythread = new ThreadLock();
connect(mythread, &ThreadLock::finished, this, &MainWindow::onThreadFinished);
mythread->setLogic(SM_Thread::TYPR_WAR);
mythread->start(QThread::LowPriority);
gif->start();
}
else{
}
}
void MainWindow::onThreadFinished()
{
SM_Thread type = mythread->from;
switch (type) {
case SM_Thread::TYPE_DEG:
break;
case SM_Thread::TYPE_RES:
break;
case SM_Thread::TYPE_INFO:
break;
case SM_Thread::TYPR_WAR:
QList<QString> res = mythread->res;
foreach(auto var, res) {
ui->plainTextEdit->appendPlainText(var);
//让主程序继续执行代码
QCoreApplication::processEvents();
}
break;
}
ui->label->hide();
mythread->quit();
mythread->deleteLater();
mythread->wait();
}
#include "threadlock.h"
ThreadLock::ThreadLock()
{
}
ThreadLock::~ThreadLock()
{
}
void ThreadLock::setLogic(SM_Thread var)
{
from = var;
}
void ThreadLock::run()
{
switch(from){
case TYPE_INFO :
//执行对应代码
//test();
break;
case TYPE_DEG :
//执行对应代码
//test();
break;
case TYPE_RES :
//执行对应代码
//test();
break;
case TYPR_WAR :
//执行对应代码
comIntensive();
break;
}
}
void ThreadLock::comIntensive()
{
res.clear();
do{
double resInf = ((qrand() * 1.00)/(RAND_MAX * 10)) * 1000.00;
QString resStr = QString::number(resInf,'f',2);
if(!res.contains(resStr)){
Lock.tryLockForWrite();
res.append(resStr);
Lock.unlock();
}
else{
}
}
while(!res.isEmpty() && res.size() <= 10000);
}
(3)实现效果:
该解决方法的实现思路主要是通过将复杂计算移植子线程进行运算的方法,从而避免了主线程渲染阻塞的问题。
在实际操作中我遇到了一个比较有意思的小问题,当接受到产生的数据时,我们需要将其渲染到UI界面上时,由于数据量过于庞大导致渲染速度慢,同样也会导致其他UI渲染阻塞。
针对这个问题,我采取了如下方法来保证其他的UI线程不卡顿,从而达到想要的效果。
QCoreApplication::processEvents();