QT 通过线程使界面与计算分离,解决卡顿

一、背景

1.程序未响应的原因
在项目开发中,偶尔会执行一些可能很费时的代码,比如进行文件操作,网络请求等,这些操作如果放在UI线程去做,很容易就会导致程序未响应,用户以为程序崩溃了。这是由于ui线程正在执行代码或者被阻塞住了,导致没法及时处理事件循环,界面上的UI事件无法执行,系统认为你这个程序可能挂掉了,就会出现那个未响应提示,然后弹窗问用户,要不要强制干掉这个程序。
2.最好的方法就是使用线程
开启一个单独的线程,去执行比较耗时的操作,UI主线程去执行事件循环。简而言之,界面动画操作交给主线程执行,耗时操作让另一个线程去慢慢执行。

二、线程基础函数

1.线程
(1)void QThread::start()函数
  用户调用start()函数,会触发started信号,新创建的线程会去调用==run()==函数,从而开始执行这个线程。操作系统会根据优先级自主进行调度,如果这个线程已经在运行,则run()函数不做任何事情。
(2)int QThread::exec()
  进入事件循环并等待直到exit()被调用,将传递给exit()函数的值返回。此函数在run()函数中调用,必须调用此函数,才能启动事件处理。
(3)void QThread::exit(int returnCode = 0)
  通知线程的事件循环以返回代码退出。调用此函数后,线程将离开事件循环并从对QEventLoop::exec()的调用返回。函数的作用是:返回returnCode。
返回代码为0表示成功,任何非零值表示错误。
(4)void QThread::quit()
  告诉线程的事件循环以返回代码0退出(成功)。相当于调用QThread::exit(0),如果线程没有事件循环,则此函数不执行任何操作。
(5)[signal] void QThread::finished()
  发出finished信号时,事件循环已停止运行。除延迟删除事件外,线程中将不再处理其他事件。此信号可以连接到QObject::deleteLater(),以释放该线程中的对象。
线程终止可以调用exit()或者quit(),使用wait()可以阻塞线程,直到其他线程完成执行(或直到指定的时间过去)。
(6)睡眠函数与等待函数
  QThread还提供静态的、独立于平台的睡眠函数:sleep()、msleep()和usleep()分别为秒、毫秒和微秒。wait()和sleep()函数通常不必要使用,因为Qt是一个事件驱动的框架。请考虑监听finished()信号,代替wait()。考虑使用QTimer,从而代替sleep()函数。
2.线程执行伪代码

m_thread->start();

void start() {
	run();
}

void run() {
	//执行线程操作
	exec();
}

int exec() {
	//1.成功
	exit(0);
	return 0;
	//2.成功
	quit(0);
	return 0;
	//3.失败
	exit(-1);
	return -1;
}

三、线程实战简单使用

原理:创建一个任务类对象,并将此对象移入线程中,开启线程,线程将结果返回,然后删除任务类对象。
1.UseThread.h

class UseThread : public QWidget
{
    Q_OBJECT

public:
    explicit UseThread(QWidget *parent = nullptr);
    ~UseThread();
public slots:
    void handleResults(const QString &hand);
signals:
    void operate(const QString &);
private:
    Ui::UseThread *ui;
    QThread       m_thread;
};




class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter);

signals:
    void resultReady(const QString &result);
};

2.UseThread.cpp
耗时操作必须在工作类的槽函数中执行才行

UseThread::UseThread(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::UseThread)
{
    ui->setupUi(this);
    //1.创建工作对象
    Worker *worker = new Worker;
    //2.将工作的对象移到线程中
    worker->moveToThread(&m_thread);
    connect(&m_thread, &QThread::finished, worker, &QObject::deleteLater);
    connect(this, &UseThread::operate, worker, &Worker::doWork);
    connect(worker, &Worker::resultReady, this, &UseThread::handleResults);
    //3.线程启动,此后Worker中的函数都在线程中执行
    m_thread.start();
    //4.可向任务类传递一些参数信息
    emit operate("argv");
    
	/*需要注意的是,必须通过工作类的槽函数执行才会在线程中执行。
	  假如,在当前构造函数中直接这样调worker->doWork();
	  那么这个工作是不在线程中执行的,会卡的*/
}

UseThread::~UseThread()
{
    m_thread.quit();
    m_thread.wait();
    delete ui;
}

void UseThread::handleResults(const QString &hand)
{
    //任务执行结果返回,进行一些处理
    qDebug()<<hand;
}


void Worker::doWork(const QString &parameter) 
{
    Q_UNUSED(parameter);
    QString result = "china";
    //这里执行比较复杂的事情,如copy file、net quest、
    
    emit resultReady(result);
}

上面这种写法,是通过moveToThread这种写法,你可以通过信号和槽的方式来开启线程。但是还有一种方式是通过Worker类去继承QTthread的方式去启动线程。但是这种方式必须先启动线程,然后去重写Worker的run函数,耗时操作在run函数中执行。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值