文章目录
- 1. 概述
- 2. 方法描述
- 3. 代码:
- 4. 运行结果及说明
- 5. 注意事项
- 6. 结语
1. 概述
QThread 类提供了一个与平台无关的管理线程的方法。一个 QThread 对象管理一个线程。
QThread 的执行从 run() 函数的执行开始,在 Qt 自带的 QThread 类中,run() 函数通过调用 exec() 函数来启动事件循环机制,并且在线程内部处理 Qt 的事件。
在 Qt 中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。QThread 的使用方法有如下两种:
- QObject::moveToThread()
- 继承 QThread 类
下面通过具体的方法描述和例子来介绍第一种方法,第二种方法在下一篇文章中介绍。
2. 方法描述
在要使用线程的 controller 类中,新建一个 QThread 的对象和 woker 类对象,使用 moveToThread() 方法将 worker 对象的事件循环全部交由 QThread 对象处理。
Worker* worker = new Worker ; //创建一个QObject对象
QThread* _workerThread = new QThread(this); //创建一个新线程
worker->moveToThread(&_workerThread); // 调用 moveToThread 将该任务交给 workThread
如果这个 Worker 对象有槽函数,那么这些槽函数就会在新线程中执行, 如下类图中 dowork() 槽函数,就是新的线程中执行。
3. 代码:
首先新建一个 work 类,该类重点在于其 doWork 槽函数,这个函数定义了线程需要做的工作,需要向其发送信号来触发槽函数。
//
// Worker.h
//
#ifndef DEL_WORKER_H
#define DEL_WORKER_H
#include <QObject>
#include <QDebug>
#include <QThread>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void doWork(int parameter); // doWork 定义了线程要执行的操作
signals:
void resultReady(const int result); // 线程完成工作时发送的信号
};
#endif //DEL_WORKER_H
//
// Worker.cpp
//
#include "Worker.h"
Worker::Worker(QObject *parent) {
}
void Worker::doWork(int parameter) {
qDebug() << "receive the execute signal" ;
qDebug() << "\tCurrent thread ID: " << QThread::currentThreadId();
qDebug() << "\tFinish the work and sent the result Ready signal\n" ;
emit resultReady(parameter);
}
然后定义一个 Controller 类,这个类中定义了一个 QThread 对象,用于处理 worker 对象的事件循环工作。
//
// Controller.h
//
#ifndef DEL_CONTROLLER_H
#define DEL_CONTROLLER_H
#include <QObject>
#include <QThread>
#include <QDebug>
#include "Worker.h"
// controller 用于 启动子线程 和 处理子线程执行的结果
class Controller : public QObject
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr);
~Controller() override ;
public slots:
static void handleResults(int result); // 处理子线程执行的结果
signals:
void operate(const int); // 发送信号,触发线程
private:
QThread* _workerThread = nullptr;
};
#endif //DEL_CONTROLLER_H
//
// Controller.cpp
//
/* 在 构造函数中创建 worker 对象,并且将其事件循环全部交给_workerThread 对象来处理,最后启动该线程,然后触发其事件处理函数。*/
#include "Controller.h"
Controller::Controller(QObject *parent) : QObject(parent) {
auto *worker = new Worker ;
_workerThread= new QThread(this);
// 调用 moveToThread 将该任务交给 workThread
worker->moveToThread(&_workerThread);
// operate 信号发射后启动线程工作
connect(this, SIGNAL(operate(const int)), worker, SLOT(doWork(int)));
// 该线程结束时销毁
connect(&_workerThread, &QThread::finished, worker, &QObject::deleteLater);
// 线程结束后发送信号,对结果进行处理
connect(worker, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
// 启动线程
_workerThread.start();
// 发射信号,开始执行
qDebug() << "emit the signal to execute!" ;
qDebug() << "\tCurrent thread ID:" << QThread::currentThreadId() << '\n' ;
emit operate(0);
}
// 析构函数中调用 quit() 函数结束线程
Controller::~Controller() {
_workerThread.quit();
_workerThread.wait();
}
void Controller::handleResults(const int result) {
qDebug() << "receive the resultReady signal" ;
qDebug() << "\tCurrent thread ID: " << QThread::currentThreadId() << '\n' ;
qDebug() << "\tThe last result is: " << result ;
}
接下来就是 main 函数,主函数中我们新建一个 Controller 对象,开始执行:
#include <QApplication>
#include <QThread>
#include "Controller.h"
int main(int argc, char *argv[])
{
qDebug() << "I am main Thread, my ID: " << QThread::currentThreadId() << "\n" ;
QApplication a(argc, argv);
Controller c ;
return a.exec();
}
4. 运行结果及说明
main 函数中打印当前线程编号,即主线程的线程编号是 0x7f4078b2b740,在 Controller 的构造函数中继续打印当前线程编号,也是主线程编号,之后把 work 类的工作交给子线程后,给子线程发送信号,子线程收到了信号开始执行,其线程号为0x7f404fd3c700,执行结束后发送信号给 Controller 处理结果。
5. 注意事项
在使用moveToThread函数时,需要注意以下几点:
- 只有QObject对象可以使用moveToThread函数,其他对象不能使用。
- 一旦调用了moveToThread函数,这个对象的线程上下文就会改变,因此在调用该函数之后,这个对象所属的线程上下文不能再使用。
- 如果对象正在执行某个函数,而该函数又没有使用线程锁,那么在移动对象之后,该函数仍然会在原来的线程中执行。因此,在移动对象之前,需要确保该对象不处于执行状态。
- 如果一个QObject对象的子对象也需要移动到新线程中,那么这些子对象也必须调用moveToThread函数进行移动。
6. 结语
moveToThread 方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个槽函数,再建立触发这些槽函数的信号,然后连接信号和槽,最后调用 moveToThread 方法将这个类交给一个 QThread 对象,再调用 QThread 的 start() 函数使其全权处理事件循环。于是,任何时候我们需要让子线程执行某个任务,只需要发出对应的信号就可以。
其优点是我们可以在一个 worker 类中定义很多个需要做的工作,然后触发信号,子线程就可以执行。相比于继承 QThread 方法,只能执行 run() 函数中的任务,moveToThread 的方法中一个线程可以做很多不同的工作,只要实现对应的槽函数,触发对应的信号即可。
参考: QT 中的多线程—moveToThread 篇