Qt界面卡死问题探讨及解决方法

1.前言

在 Qt 开发中,GUI 程序有一个基本准则:所有耗时的操作都不应放在主线程(GUI 线程)中执行。 主线程负责维护事件循环和处理界面更新,一旦被耗时任务阻塞,界面就会卡死导致无法拖动、按钮无响应、进度条不刷新等问题。

2.原因

Qt 程序的 main() 函数末尾通常会调用 QApplication::exec()启动一个事件循环(event loop)。事件循环不断从队列中取出事件(鼠标点击、重绘、定时器、信号槽调用等)并处理。如果我们在主线程的某个槽函数里写了一个耗时的任务,那么在这个循环结束之前,事件循环无法处理任何新事件,导致界面无法重绘,呈现出卡死状态。

3.解决方法

QThread+moveToThread

4.代码示例

MainWindow.h

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>

class Worker;

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 StartButtonClicked();// 点击开始计算按钮
    void updateProgress(int percent);// 进度条更新百分比
    void showResult(long long result);// 展示累加计算结果
    void onWorkerFinished();// 任务完成,按钮恢复

private:
    Ui::MainWindow *ui;
    QThread *worker_thread_;// 工作线程
    Worker *worker_;// 工作对象
};

#endif

MainWindow.cpp

cpp 复制代码
#include "MainWindow.h"
#include "ui_MainWindow.h"

#include "Worker.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 点击开始计算按钮
    connect(ui->startButton,&QPushButton::clicked,this,&MainWindow::StartButtonClicked);
    
    // 初始化线程和工作者对象
    worker_thread_ = new QThread(this);// 方便delete ui;时自动删除 worker_thread_
    worker_ = new Worker();// 不能有父对象,否则不能 moveToThread
    
    // 将工作对象移动到工作线程
    if(worker_) worker_->moveToThread(worker_thread_);

    // 当工作线程结束时,自动清理掉工作对象,防止内存泄漏
    connect(worker_thread_, &QThread::finished, worker_, &QObject::deleteLater);
    // 进度条更新百分比
    connect(worker_, &Worker::progressUpdated, this, &MainWindow::updateProgress);
    // 发送累加计算结果
    connect(worker_, &Worker::resultReady, this, &MainWindow::showResult);
    // 任务完成,按钮恢复
    connect(worker_, &Worker::finished, this, &MainWindow::onWorkerFinished);
}

MainWindow::~MainWindow()
{
    // 结束线程并安全销毁
    if (worker_thread_ && worker_thread_->isRunning()) 
    {
        worker_thread_->quit();// 自动调用worker_thread_的finished信号清理工作对象,无需再手动delete worker_
        worker_thread_->wait();// 阻塞等待,直到线程完全退出
    }
    
    delete ui;// 对象树删除 worker_thread_
}

// 点击开始计算按钮
void MainWindow::StartButtonClicked()
{
    // 禁用开始按钮,防止重复启动
    ui->startButton->setEnabled(false);
    // 清空计算结果
    ui->statusLabel->clear();

    // 启动线程
    if(worker_thread_ && !worker_thread_->isRunning()) worker_thread_->start();

    // 通过信号槽调用 Worker::doHeavyLoop,这个槽会在工作线程中执行
    // 参数 200000000 表示 2 亿次循环,模拟耗时任务
    QMetaObject::invokeMethod(worker_, "doHeavyLoop",
                              Qt::QueuedConnection,
                              Q_ARG(long, 200000000));
}

// 进度条更新百分比
void MainWindow::updateProgress(int percent)
{
    ui->progressBar->setValue(percent);
}

// 展示累加计算结果
void MainWindow::showResult(long long result)
{
    ui->statusLabel->setText(QString("计算结果: %1").arg(result));
}

// 任务完成,按钮恢复
void MainWindow::onWorkerFinished()
{
    ui->startButton->setEnabled(true);
}

Worker.h

cpp 复制代码
#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);

public slots:
    // 该槽函数会在工作线程中执行
    void doHeavyLoop(long loopCount);

signals:
    // 进度条更新百分比
    void progressUpdated(int percent);
    // 发送累加计算结果
    void resultReady(long long result);
    // 任务完成信号
    void finished();
};

#endif

Worker.cpp

cpp 复制代码
#include "Worker.h"
#include <QThread>

Worker::Worker(QObject *parent) : QObject(parent) 
{
    
}

void Worker::doHeavyLoop(long loopCount)
{
    // 模拟一个耗时的累加循环
    long long sum = 0;
    for (long long i = 1; i <= loopCount; ++i) 
    {
        sum += i;
        // 每迭代 1% 发送一次进度,避免信号过密
        if (i % (loopCount / 100) == 0) 
        {
            int percent = (i * 100) / loopCount;
            emit progressUpdated(percent);
            // 额外加一个微小延时,让信号有机会得到处理(非必须,但便于观察)
           //  QThread::msleep(1000);// 1秒
        }
    }
    // 发送累加计算结果
    emit resultReady(sum);
    // 任务完成,停止线程
    emit finished();
}

5.运行结果

参考资料:https://mp.weixin.qq.com/s/xfunHOiD4_NDCs-uWHU-lg

相关推荐
bug和崩溃我都要6 小时前
Qt 封装 libmpv 全功能视频播放器开发指南
开发语言·qt·音视频
郝学胜-神的一滴6 小时前
Qt 高级开发 018:复刻经典登录界面布局与窗口美化全解析
开发语言·c++·qt·程序人生·用户界面
小新1106 小时前
qt creator 将qInfo的输出日志写入日志文档,方便查看
开发语言·qt
hssfscv7 小时前
QT的学习记录1
开发语言·qt·学习
yong99908 小时前
基于Qt的文件传输系统
开发语言·qt
誰能久伴不乏8 小时前
ibmodbus “Invalid argument“ 错误的排查与修复
c++·qt·modbus
肥or胖9 小时前
Qt中OpenGL快速入门
qt·音视频·opengl
程序员如山石12 小时前
QT标签左侧水平显示
qt
xcyxiner12 小时前
ubuntu下 cmake初始化脚本 以及 qt依赖
c++·qt