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

相关推荐
skr爱码士7 分钟前
03_第一个Qt项目:Hello World 的完整拆解
c++·qt·客户端
ryanuo726 分钟前
Shadcn/ui × Qt 6/QML:一次从 Web UI 到桌面 UI 的组件化实践
c++·qt·ui·shadcn
不会代码的小猴40 分钟前
6. Qt网络编程
开发语言·c++·笔记·qt·算法
不会代码的小猴12 小时前
3. 控件学习1
开发语言·c++·笔记·qt·算法
精英的英17 小时前
记一次 Qt5 Language Server 开发
开发语言·vscode·qt
平常心的技术小牛19 小时前
Qt-快速上手-QTextCursor
开发语言·qt
不会代码的小猴1 天前
4. 控件学习2
开发语言·c++·笔记·qt
我是小灰灰吖1 天前
解决高频率图像处理中的内存“只增不减”:glibc malloc_trim实战指南
linux·qt
秋田君1 天前
QT_一个UDP通信程序(单播+广播)
开发语言·qt·udp
我是小灰灰吖1 天前
Qt 6.9.3 Ubuntu 22.04 虚拟键盘显示问题全记录与解决方案
linux·qt·ubuntu