QObject::moveToThread: Cannot move objects with a parent
我们看到MainWindow类中是这样定义InheritQThread类对象的:WorkerTh = new InheritQThread(this)。如果需要使用moveToThread()来改变对象的依附性,其创建时不能够带有父类。将语句改为:WorkerTh = new InheritQThread()即可。
(2)修改完成后,点击【start】启动线程,然后点击【stop】按钮跳出run函数中的while循环,最后点击【check thread state】按钮来检查线程的状态,会是什么样的情况呢?
由上图可以看到,线程依旧处于运行状态,这是因为run函数中调用了exec(),此时线程正处于事件循环中。
(3)接下来再点击【Send QdebugSignal】按钮来发送QdebugSignal信号。
由终端的打印信息得知,InheritQThread::QdebugSlot槽函数是在子线程中执行的。
四、子类化QThread线程的信号与槽
从上图可知,事件循环是一个无止尽循环,事件循环结束之前,exec()函数后的语句无法得到执行。只有槽函数所在线程开启了事件循环,它才能在对应信号发射后被调用。无论事件循环是否开启,信号发送后会直接进入槽函数所依附的线程的事件队列,然而,只有开启了事件循环,对应的槽函数才会在线程中得到调用。下面通过几种情况来验证下:
(1)代码和《三、使用事件循环》小节的代码一样,然后进行如下的操作:点击【start】按钮->再点击【Send QdebugSignal】按钮,这个时候槽函数会不会被执行呢?
这种情况无论点多少次发送QdebugSignal信号,InheritQThread::QdebugSlot槽函数都不会执行。因为当前线程还处于while循环当中,如果需要实现槽函数在当前线程中执行,那么当前线程就应该处于事件循环的状态,也就是正在执行exec()函数。所以如果需要InheritQThread::QdebugSlot槽函数执行,就需要点击【stop】按钮退出while循环,让线程进入事件循环。
(2)在《三、使用事件循环》小节的代码基础上,把InheritQThread::run函数删除,然后进行如下的操作:点击【start】启动线程->点击【stop】按钮跳出run函数中的while循环进入事件循环->点击【Send QdebugSignal】按钮来发送QdebugSignal信号,会有什么结果呢?
结果会和上面第一种情况一样,虽然信号已经在子线程的事件队列上,但是由于子线程没有事件循环,所以槽函数永远都不会被执行。
(3)在上面《三、使用事件循环》小节的代码基础上,将InheritQThread构造函数中的 moveToThread(this) 去除掉。进行如下操作:点击【start】启动线程->点击【stop】按钮跳出run函数中的while循环进入事件循环->点击【Send QdebugSignal】按钮来发送QdebugSignal信号,会有什么结果呢?
由上图可以看出InheritQThread::QdebugSlot槽函数居然是在GUI主线程中执行了。因为InheritQThread对象我们是在主线程中new出来的,如果不使用moveToThread(this)来改变对象的依附性关系,那么InheritQThread对象就是属于GUI主线程,根据connect信号槽的执行规则,最终槽函数会在对象所依赖的线程中执行。信号与槽绑定的connect函数的细节会在后期的《跨线程的信号槽》文章进行单独介绍。
五、如何正确退出线程并释放资源
InheritQThread类的代码不变动,和上述的代码一样:
#ifndef INHERITQTHREAD_H
#define INHERITQTHREAD_H
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QDebug>
class InheritQThread:public QThread
Q_OBJECT
public:
InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){
moveToThread(this);
void StopThread(){
QMutexLocker lock(&m_lock);
m_flag = false;
protected:
//线程执行函数
void run(){
qDebug()<<"child thread = "<<QThread::currentThreadId();
int i=0;
m_flag = true;
while(1)
emit ValueChanged(i);
QThread::sleep(1);
QMutexLocker lock(&m_lock);
if( !m_flag )
break;
exec();
signals:
void ValueChanged(int i);
public slots:
void QdebugSlot(){
qDebug()<<"QdebugSlot function is in thread:"<<QThread::currentThreadId();
public:
bool m_flag;
QMutex m_lock;
#endif // INHERITQTHREAD_H
MainWindow类添加ExitBt、TerminateBt两个按钮,用于调用WorkerTh->exit(0)、WorkerTh->terminate()退出线程函数。由往期《QThread源码浅析》文章中《QThread::quit()、QThread::exit()、QThread::terminate()源码》小节得知调用quit和exit是一样的,所以本处只添加了ExitBt按钮:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "ui_mainwindow.h"
#include "InheritQThread.h"
#include <QThread>
#include <QDebug>
namespace Ui {
class MainWindow;
class MainWindow : public QMainWindow
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr) :
QMainWindow(parent),
ui(new Ui::MainWindow){
qDebug()<<"GUI thread = "<<QThread::currentThreadId();
ui->setupUi(this);
WorkerTh = new InheritQThread();
connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue);
connect(this, &MainWindow::QdebugSignal, WorkerTh, &InheritQThread::QdebugSlot);
~MainWindow(){
delete ui;
signals:
void QdebugSignal();
public slots:
void setValue(int i){
ui->lcdNumber->display(i);
private slots:
void on_startBt_clicked(){
WorkerTh->start();
void on_stopBt_clicked(){
WorkerTh->StopThread();
void on_checkBt_clicked(){
if(WorkerTh->isRunning()){
ui->label->setText("Running");
}else{
ui->label->setText("Finished");
void on_SendQdebugSignalBt_clicked(){
emit QdebugSignal();
void on_ExitBt_clicked(){
WorkerTh->exit(0);
void on_TerminateBt_clicked(){
WorkerTh->terminate();
private:
Ui::MainWindow *ui;
InheritQThread *WorkerTh;
#endif // MAINWINDOW_H
运行上述的例程,点击【start】启动线程按钮,然后直接点击【exit(0)】或者【terminate()】,这样会直接退出线程吗?
点击【exit(0)】按钮(猛点)
点击【terminate()】按钮(就点一点)
由上述情况我们可以看到上面例程的线程启动之后,无论怎么点击【start】按钮,线程都不会退出,点击【terminate()】按钮的时候就会立刻退出当前线程。由往期《QThread源码浅析》文章中《QThread::quit()、QThread::exit()、QThread::terminate()源码》小节可以得知,若使用QThread::quit()、QThread::exit()来退出线程,该线程就必须要在事件循环的状态(也就是正在执行exec()),线程才会退出。而QThread::terminate()不管线程处于哪种状态都会强制退出线程,但这个函数存在非常多不安定因素,不推荐使用。我们下面来看看如何正确退出线程。
(1)如何正确退出线程?
如果线程内没有事件循环,那么只需要用一个标志变量来跳出run函数的while循环,这就可以正常退出线程了。
如果线程内有事件循环,那么就需要调用QThread::quit()或者QThread::exit()来结束事件循环。像刚刚举的例程,不仅有while循环,循环后面又有exec(),那么这种情况就需要先让线程跳出while循环,然后再调用QThread::quit()或者QThread::exit()来结束事件循环。如下:
注意:尽量不要使用QThread::terminate()来结束线程,这个函数存在非常多不安定因素。
(2)如何正确释放线程资源?
退出线程不代表线程的资源就释放了,退出线程只是把线程停止了而已,那么QThread类或者QThread派生类的资源应该如何释放呢?直接 delete QThread类或者派生类的指针吗?当然不能这样做,千万别手动delete线程指针,手动delete会发生不可预料的意外。理论上所有QObject都不应该手动delete,如果没有多线程,手动delete可能不会发生问题,但是多线程情况下delete非常容易出问题,那是因为有可能你要删除的这个对象在Qt的事件循环里还排队,但你却已经在外面删除了它,这样程序会发生崩溃。 线程资源释放分为两种情况,一种是在创建QThread派生类时,添加了父对象,例如在MainWindow类中WorkerTh = new InheritQThread(this)让主窗体作为InheritQThread对象的父类;另一种是不设置任何父类,例如在MainWindow类中WorkerTh = new InheritQThread()。
1、创建QThread派生类,有设置父类的情况:
这种情况,QThread派生类的资源都让父类接管了,当父对象被销毁时,QThread派生类对象也会被父类delete掉,我们无需显示delete销毁资源。但是子线程还没结束完,主线程就destroy掉了(WorkerTh的父类是主线程窗口,主线程窗口如果没等子线程结束就destroy的话,会顺手把WorkerTh也delete这时就会奔溃了)。 注意:这种情况不能使用moveToThread(this)改变对象的依附性。 因此我们应该把上面MainWindow类的构造函数改为如下:
~MainWindow(){
WorkerTh->StopThread();//先让线程退出while循环
WorkerTh->exit();//退出线程事件循环
WorkerTh->wait();//挂起当前线程,等待WorkerTh子线程结束
delete ui;
2、创建QThread派生类,没有设置父类的情况:
也就是没有任何父类接管资源了,又不能直接delete QThread派生类对象的指针,但是QObject类中有 void QObject::deleteLater () [slot] 这个槽,这个槽非常有用,后面会经常用到它用于安全的线程资源销毁。我们通过查看往期《QThread源码浅析》文章中《QThreadPrivate::start()源码》小节可知线程结束之后会发出 QThread::finished() 的信号,我们将这个信号和 deleteLater 槽绑定,线程结束后调用deleteLater来销毁分配的内存。
在MainWindow类构造函数中,添加以下代码:
connect(WorkerTh, &QThread::finished, WorkerTh, &QObject::deleteLater)
~MainWindow()析构函数可以把 wait()函数去掉了,因为该线程的资源已经不是让主窗口来接管了。当我们启动线程之后,然后退出主窗口或者直接点击【stop】+【exit()】按钮的时候,会出现以下的警告:
QThread::wait: Thread tried to wait on itself
QThread: Destroyed while thread is still running
为了让子线程能够响应信号并在子线程执行槽函数,我们在InheritQThread类构造函数中添加了 moveToThread(this) ,此方法是官方极其不推荐使用的方法。那么现在我们就遇到了由于这个方法引发的问题,我们把moveToThread(this)删除,程序就可以正常结束和释放资源了。那如果要让子线程能够响应信号并在子线程执行槽函数,这应该怎么做?在下一期会介绍一个官方推荐的《子类化QObject+moveToThread》的方法。
QThread只有run函数是在新线程里;
如果必须需要实现在线程内执行槽的情景,那就需要在QThread的派生类构造函数中调用moveToThread(this),并且在run函数内执行QThread::exec()开启事件循环;(极其不推荐使用moveToThread(this),下一期会介绍一种安全可靠的方法)
若需要使用事件循环,需要在run函数中调用QThread::exec();
尽量不要使用terminate()来结束线程,可以使用bool标志位退出或者在线程处于事件循环时调用QThread::quit、QThread::exit来退出线程;
善用QObject::deleteLater来进行内存管理;
在QThread执行start函数之后,run函数还未运行完毕,再次start,不会发生任何结果;
子类化QThread多线程的方法适用于后台执行长时间的耗时操作、单任务执行的、无需在线程内执行槽的情景。
这个是本文章实例的源码地址:https://gitee.com/CogenCG/QThreadExample.git