QThread
是Qt中用于在多线程中运行代码的核心类,它是QObject的一个子类。
关于QThread如何使用,Qt官方提供了两种方法:
步骤一:子类化QThread并重新实现run()。
步骤二:然后创建子类的实例并调用start()成员函数运行线程。
如下代码片段:
class MyThread : public QThread {
private:
void run() override {
// 要在新线程中运行的代码
}
};
MyThread *thread = new MyThread;
thread->start(); // 调用run()启动一个新线程
// ...
thread->wait(); // 等待线程完成
线程具有优先级,在调用start()的时候可以指定优先级参数,或使用setPriority()更改线程的优先级。
当从run()返回时(一段时间后)线程将停止运行,具体的时间长短与操作系统相关。
QThread::isRunning()
和QThread::isFinished()
提供关于线程执行的信息,在开发中,可以通过这两个成员函数获取到线程的运行状态。
QThread
提供了QThread::started()(当线程开始时发射)和QThread::finished()(在线程运行结束后发射)两个信号。
一个线程可以通过调用QThread:sleep()函数临时停止执行。通常这是一个不好的使用方法,但是比事件驱动(或轮询)要好得多。
通过QThread调用wait()来等待线程执行完成,可选择性向该函数传递等待的最大毫秒数。
第二种使用QThread创建、管理线程的方法:本质上不是派生QThread,而是对象关联
。步骤如下:
(1)创建QThread
。
(2)使用moveToThread()
将对象添加到QThread管理的线程中运行。
如下代码片段:
auto thread = new QThread;
auto worker = new Worker;
connect(thread, &QThread::started, worker, &Worker::doWork);
connect(worker, &Worker::workDone, thread, &QThread::quit);
connect(thread, &QThread::finished, worker, &Worker::deleteLater);
worker->moveToThread(thread);
thread->start();
QThread的第二种使用方法,允许我们在多线程中运行代码而不需要子类化QThread。
在线程中运行代码有两种基本策略:不带事件循环
和带事件循环
(1)不带事件循环
派生类QThread并重载QThread::run()
成员函数。创建一个实例并使用QThread::start()
启动新线程。
class MyThread : public QThread {
private:
void run() override {
loadFilesFromDisk();
doCalculations();
saveResults();
}
};
auto thread = new MyThread;
thread->start();
thread->wait();
(2)带事件循环
当在处理定时器、网络、排队信号/槽链接等问题时,事件循环是必须的。
每个线程的本地事件循环都为存在于该线程中的QObjects
交付事件,如果没有事件循环,存在于对应线程中的对象将接收不到事件。
通过在run()中调用QThread::exec()
来启动线程的本地事件循环。如下代码片段:
class MyThread : public QThread {
private:
void run() override {
auto socket = new QTcpSocket;
socket->connectToHost(...);
exec(); // 运行事件循环
// 执行一些清除操作
}
};
QThread::quit()
或QThread::exit()
将退出事件循环。可以使用QEventLoop或者手动调用QCoreApplication::processEvents()。
注:QThread::run()的默认实现实际上调用了QThread: exec ()。如果重载了QThread::run(),我们就必须手动调用QThread: exec ()
。
在非主线程中不能执行以下四种类型的操作:
(1)不能执行对GUI 控件的操作。包括但不限于:使用任何QWidget / Qt Quick / QPixmap API。这些情况是除外:使用QImage, QPainter等;使用OpenGL可能是可以的:但是需要在运行时调用QOpenGLContext: supportsThreadedOpenGL ()
事先检查。
(2)不能调用Application::exec()。
(3)在销毁相应QThread
对象之前,一定要销毁次要线程中的所有QObject。【这一点非常重要】。
(4)不要阻塞GUI线程。
如何确保QObjects的被销毁?如下:
(1)在QThread::run()堆栈上创建QObject
。
(2)将他们的QObject::deleteLater()
槽函数连接到QThread::finished()
信号。
(3)把QObject
从线程里移出来。
例如以下代码片段:
class MyThread : public QThread {
private:
void run() override {
MyQObject obj1, obj2, obj3;
QScopedPointer<OtherQObject> p;
if (condition)
p.reset(new OtherQObject);
auto anotherObj = new AnotherQObject;
connect(this, &QThread::finished,anotherObj, &QObject::deleteLater);
auto yetAnother = new YetAnotherQObject;
// 在退出线程之前,将该对象移动到主线程中
yetAnother->moveToThread(qApp->thread());
// 从这一行代码后,就不要使用或操作到这个QObject!
}
};
Qt下多线程的开发,使用QThread
比较方便。本文记录了使用QThread的两种方法,以及QThread中线程运行的事件交付方式,也记录关于QThread的一些注意事项。