Qt页面卡死的几种情况及解决办法

1.主界面卡死的几种情况及处理办法

1.1 页面未响应的本质

qtui框架是基于事件循环的,一般,不建议在ui线程中处理比较复杂的耗时操作,局部长时间循环操作,批量下载操作,如果页面中这些耗时操作的逻辑处理不好时,会导致ui短暂性卡死,对于由于上述操作引起的卡其,可以处理的办法有以下几种

2.1逻辑一:在ui所在线程内,进行大批量的循环操作

例如,如下代码

auto process = [] (){
    for(int i = 0; i < 100000000;i++){
        //do somethings you want to do 
    }
}

假设将上述逻辑与一个按钮事件绑定,当点击按钮时,之后的短时间内会造成页面的卡死,ui无法响应各种事件,只能等到for循环执行完成之后才可以响应页面事件。

2.2对于上述代码,做如下修改,可以避免页面假死
connect(ui->btn,&QPushButton::clicked,this,[=](){
    
    for(int i = 0; i < 10000000000;i++)
    {
        //do somethings you want to do 
        QCoreApplication::processEvents();
    }
});

加入QCoreApplication::processEvents();根据官方文档解释:
调用此函数只处理调用线程的事件,并在所有可用事件处理完毕后返回。可用事件是在函数调用之前排队的事件。这意味着在函数运行时发布的事件将被放入队列,直到下一轮的事件处理,该函数可以以手动的方式处理事件队列中的事件

2.3逻辑二:弱网环境下,需要下载一张很大的图片

假设耗时操作不是一个循环内,而是存在这样一种情况,在一个弱网环境下,需要下载一张很大的图片的时候,假设该下载动作是在ui线程中执行的时候,应该怎么办?使用QtConcurrent::run()
使用QtConCurrent,这个类是将一个函数放到新的线程里来执行再加上QFuture<T> 这个类,可以控制这个新的线程的函数开始,控制,结束等操作,具体方法如下:

static bool moreTimeFunction(){
    bool t = downLoadBigPicture(); //假设该函数很耗时,图片很大,网速很差
}

auto okayOperation= [](){
    QFuture<bool> future = QtConcurrent::run(downLoadBigPicture);
    while(!future.isFinished()){
        QApplication::processEvents(QEventLoop::AllEvents.100)
    }

}
connection(ui->btn,QPushButton::clicked,this,&ClassName::okayOperation);

QFutuwre+QtConCurrent框架非常强大,可以将线程同步,异步的状态抽象出来,QtConCurrent是Qt封装的一个线程池,使用时需要在pro文件中配置,再加该命名空间便可以使用。

2.4使用QThread另起线程

通常处理ui卡死最常用的办法,就是将耗时操作放进线程中执行,等待线程执行完毕后发信号出来通知,当前耗时操作已经被执行完,允许进入下一环节。
Qt线程创建的方式有两种,一种是继承至QThread,之后重写run()方法来实现线程的创建,另一种是继承至QObject,通过moveToThread的方式创建,官方推荐使用后者,两者的区别是:
moveToThread 方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个槽函数,再建立触发这些槽函数的信号,然后连接信号和槽,最后调用 moveToThread 方法将这个类交给一个 QThread 对象,再调用 QThread 的 start() 函数使其全权处理事件循环。于是,任何时候我们需要让子线程执行某个任务,只需要发出对应的信号就可以。
其优点是我们可以在一个worker类中定义很多个需要做的工作,然后触发信号,子线程就可以执行。相比于继承 QThread 方法,只能执行 run() 函数中的任务,moveToThread 的方法中一个线程可以做很多不同的工作,只要实现对应的槽函数,触发对应的信号即可,针对第二种方法,有如下代码

#ifndef DEL_WORKER_H
#define DEL_WORKER_H

#include <QObject>
#include <QDebug>
#include <QThread>

class Worker : public QObject
{
Q_OBJECT

public:
    explicit Worker(QObject *parent = nullptr){
    
    }

public slots:
    void doWork(int parameter);  // doWork 定义了线程要执行的操作
    {
        qDebug() << "receive the execute signal" ;
        qDebug() << "\tCurrent thread ID: " << QThread::currentThreadId();

    // 循环一百万次
        for (int i = 0; i != 1000000; ++i)
        {
            ++parameter ;
        }

        // 发送结束信号
        qDebug() << "\tFinish the work and sent the result Ready signal\n" ;

        emit resultReady(parameter);
    }

signals:
    void resultReady(const int result);  // 线程完成工作时发送的信号
};

#endif //DEL_WORKER_H
2.5.使用moveToThread开启线程
//
// Controller.h
//

#ifndef DEL_CONTROLLER_H
#define DEL_CONTROLLER_H

#include <QObject>
#include <QThread>
#include <QDebug>

#include "Worker.h"

// controller 用于 启动子线程 和 处理子线程执行的结果
class Controller : public QObject
{
Q_OBJECT

    QThread workerThread ;

public:
    explicit Controller(QObject *parent = nullptr){
    
        auto *worker = new Worker ;

    // 调用 moveToThread 将该任务交给 workThread
        worker->moveToThread(&workerThread);

        // operate 信号发射后启动线程工作
        connect(this, SIGNAL(operate(const int)), worker, SLOT(doWork(int)));

        // 该线程结束时销毁
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);

        // 线程结束后发送信号,对结果进行处理
        connect(worker, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));

        // 启动线程
        workerThread.start();

        // 发射信号,开始执行
        qDebug() << "emit the signal to execute!" ;
        qDebug() << "\tCurrent thread ID:" << QThread::currentThreadId() << '\n' ;

        emit operate(0);
    }

    ~Controller(){
        workerThread.quit();
        workerThread.wait();
    };

public slots:
    static void handleResults(int result);  // 处理子线程执行的结果
    {
        qDebug() << "receive the resultReady signal" ;
        qDebug() << "\tCurrent thread ID: " << QThread::currentThreadId() << '\n' ;
        qDebug() << "\tThe last result is: " << result ;
    }

signals:
    void operate(const int);  // 发送信号,触发线程
};

#endif //DEL_CONTROLLER_H

3.小知识

1.在主线程退出之前,析构函数中总是需要先停掉子线程,这是比较合理的操作,否则系统报错误:主线程先于子线程退出
2.QThread中的静态方法currentThreadId()可以返回当前线程句柄
3.QtConCurrent的线程是线程安全型的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容