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

相关推荐
xcyxiner17 小时前
DicomViewer (目录调整) 2
qt
xcyxiner19 小时前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能3 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G3 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt
森G3 天前
77、线程池原理和实现------服务器源码解析----云视频服务项目
服务器·c++·qt
森G3 天前
71、打包发布---------打包发布
c++·qt
初圣魔门首席弟子3 天前
Node.js 详细介绍(知识库版)
windows·qt·node.js·知识库
C++ 老炮儿的技术栈3 天前
Qt工控实战:自研机器人TCP长连接客户端(粘包处理+心跳保活+自动重连完整源码解析)
qt·tcp/ip·机器人
郝学胜-神的一滴3 天前
CMake 019:程序生成与清理全解析
开发语言·c++·qt·程序人生·软件构建·cmake
森G3 天前
76、仿ASIO实现的Linux c++服务器------服务器源码解析----云视频服务项目
c++·qt