一、背景
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 ¶meter);
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 ¶meter)
{
Q_UNUSED(parameter);
QString result = "china";
//这里执行比较复杂的事情,如copy file、net quest、
emit resultReady(result);
}
上面这种写法,是通过moveToThread这种写法,你可以通过信号和槽的方式来开启线程。但是还有一种方式是通过Worker类去继承QTthread的方式去启动线程。但是这种方式必须先启动线程,然后去重写Worker的run函数,耗时操作在run函数中执行。