Qt moveToThread使用及注意事项

在Qt中,每个QObject对象都有一个线程关联,这个线程被称为对象的“线程上下文”。默认情况下,一个QObject对象的线程上下文与创建它的线程相同。也就是说,如果我们在主线程中创建了一个QObject对象,那么这个对象的线程上下文就是主线程。

在某些情况下,我们可能需要将一个QObject对象(或继承QObject的对象)移动到另外一个线程中执行,这时就可以使用QObject的moveToThread函数。调用该函数后,会切换这个QObject对象及其子对象的线程上下文切换到新的线程中,这意味着这个对象的所有信号和槽函数都会在新的线程中执行。这样耗时的工作就会在另一个线程中执行,不会卡主界面。

注意:子对象的含义是指QObject对象内创建的QObject对象,这个内部创建的对象的父对象是QObject对象,即内部对象初始化时把this指针传入作为父对象指针。

 

基本使用

查看代码
QThread* thread = new QThread(); // 创建一个新线程
Worker *my_worker= new Worker; // 创建一个工作耗时的对象,继承QObject类
my_worker->moveToThread(thread); // 将my_worker对象移动到新线程中
//线程析构时发出finished信号,此时连接的槽函数可以用来释放线程中的工作类
connect(&thread, &QThread::finished, my_worker, &QObject::deleteLater);
thread.start(); //启动线程,并进入事件循环

 在这个例子中,由于my_worker对象没有实现任何槽函数,所以在新线程中并没有什么可执行的代码。但是,如果这个对象有槽函数,那么这些槽函数就会在这个新线程中执行。

 

在使用moveToThread函数时,需要注意以下几点:

  • 只有QObject对象可以使用moveToThread函数,其他对象不能使用。
  • 一旦调用了moveToThread函数,这个对象的线程上下文就会改变,因此在调用该函数之后,这个对象所属的线程上下文不能再使用。
  • 如果对象正在执行某个函数,而该函数又没有使用线程锁,那么在移动对象之后,该函数仍然会在原来的线程中执行。因此,在移动对象之前,需要确保该对象不处于执行状态。
  • 如果一个QObject对象的内部对象并没有指定父对象是这个QObject对象,且内部对象也要移动到子线程中,则需要单独操作,就像移动这个QObject对象一样。若QObject对象的内部对象指定了父对象是这个QObject对象,那么这个内部对象会自动一起移动到子线程。
  • 线程只要进行暂停休眠等待,一定会阻塞事件循环,无论是在子线程还是在主线程中,在子线程中执行任务时进行线程休眠了看似好像是顺序一个一个的执行,但其实是由于当前任务阻塞了事件循环,其他任务在排队等待被执行。所以如果有任务需要进行sleep休眠等待,千万不能在主线程中执行,而是放到子线程中去,就不会卡主界面,或者用 QEventLoop 来等待,而且不会卡主事件循环。
  • 要使工作类的任务在子线程中运行,要通过工作类的槽函数来运行才行,即在主线程中发送信号到子线程,然后子线程中运行对应的槽函数就是在子线程环境在运行。如果在主线程中直接调用工作类的一些方法,实际上还是在主线程中运行,并没有到子线程运行。这一切都原因是因为:通过moveToThread转移到子线程,子线程启动后其实是启动了子线程的事件循环,所以要使任务在子线程中运行,就要使任务在子线程的事件循环中触发,即通过信号与槽函数的方式。

 

 

moveToThread时QObject对象中内含子对象的几种情况

情况一:内含子对象,但并未指定父对象

查看代码
 class Son:public QObject{
    Q_OBJECT
public:
    Son(QObject* parent = nullptr):QObject(parent){
        QObject::connect(this, &Son::sig_son, this, &Son::slot_son);
    }
signals:
    void sig_son();
public slots:
    void slot_son(){
        qDebug()<<"slot_son"<<QThread::currentThreadId();
    }
private:
};

class Father:public QObject{
    Q_OBJECT
public:
    Father(QObject* parent = nullptr):QObject(parent){
        QObject::connect(this, &Father::sig_father, this, &Father::slot_father);
    }
signals:
    void sig_father();
public slots:
    void slot_father(){
        emit s.sig_son();
        qDebug()<<"slot_father"<<QThread::currentThreadId();
    }
public:
    Son s; //内含的对象,但初始化的时候并未指定Father为Son的父对象
};

...构造函数中...
thd = new QThread();
f = new Father();
f->moveToThread(thd); //Father类对象移动到子线程中
thd->start();
...

void MainWindow::on_pushButton_clicked()
{
    qDebug()<<"main pushbutton:"<<QThread::currentThreadId();
    emit f->sig_father();
}

输出:
main pushbutton: 0x31d0
slot_father 0xe20
slot_son 0x31d0

 从输出可以看到,内建对象s其实还在主线程中,并未在子线程里,因为Father对象移动到子线程的时候是将本对象以及其子对象一起移动到子线程中,但是子对象Son并没有指定父对象为Father,所以Father其实没有子对象,所以其实只有Father对象的线程上下文切换到了子线程里,而Son对象也只是个普通内建对象。注意:也只是在Father对象的槽函数中发送Son内建对象的信号,而Son内建对象的槽函数响应并不是在子线程而已。实际上在Father对象的槽函数直接调用Son内建对象的槽函数或者普通函数会发现其实还是在子线程中的,那是因为整个Father对象的槽函数就是在子线程中运行,那么子线程中做的任意事当然也在子线程中。但通过信号和槽则不一样,因为信号是能够夸线程传送的,所以看Son内建对象的信号所对应的槽函数是在哪个线程就知道该Son内建对象的线程上下文是否也转移到了子线程中。

 

情况二:内含子对象,未指定父对象,但其子对象也一起moveToThread到子线程中了

查看代码
 class Son:public QObject{
    Q_OBJECT
public:
    Son(QObject* parent = nullptr):QObject(parent){
        QObject::connect(this, &Son::sig_son, this, &Son::slot_son);
    }
signals:
    void sig_son();
public slots:
    void slot_son(){
        qDebug()<<"slot_son"<<QThread::currentThreadId();
    }
private:
};

class Father:public QObject{
    Q_OBJECT
public:
    Father(QObject* parent = nullptr):QObject(parent){
        QObject::connect(this, &Father::sig_father, this, &Father::slot_father);
    }
signals:
    void sig_father();
public slots:
    void slot_father(){
        emit s.sig_son();
        qDebug()<<"slot_father"<<QThread::currentThreadId();
    }
public:
    Son s; //内含的对象,但初始化的时候并未指定Father为Son的父对象
};

...构造函数中...
thd = new QThread();
f = new Father();
f->moveToThread(thd); //Father类对象移动到子线程中
f->s.moveToThread(thd); //Father类中的子对象也移动到子线程中
thd->start();
...

void MainWindow::on_pushButton_clicked()
{
    qDebug()<<"main pushbutton:"<<QThread::currentThreadId();
    emit f->sig_father();
}

输出:
main pushbutton2: 0x8a8c
slot_son 0x9318
slot_father 0x9318

 从输出可以看到,这时内建对象s也在子线程中了,因为它和Father类对象一起手动移动到了子线程中。

 

情况三:内含子对象,并指定父对象

查看代码
 class Son:public QObject{
    Q_OBJECT
public:
    Son(QObject* parent = nullptr):QObject(parent){
        QObject::connect(this, &Son::sig_son, this, &Son::slot_son);
    }
signals:
    void sig_son();
public slots:
    void slot_son(){
        qDebug()<<"slot_son"<<QThread::currentThreadId();
    }
private:
};

class Father:public QObject{
    Q_OBJECT
public:
	//初始化的时候让子对象s指定了父对象为本类,也就是Father类
    Father(QObject* parent = nullptr):QObject(parent),s(this){
        QObject::connect(this, &Father::sig_father, this, &Father::slot_father);
    }
signals:
    void sig_father();
public slots:
    void slot_father(){
        emit s.sig_son();
        qDebug()<<"slot_father"<<QThread::currentThreadId();
    }
public:
    Son s; //内含的对象,但初始化的时候并未指定Father为Son的父对象
};

...构造函数中...
thd = new QThread();
f = new Father();
f->moveToThread(thd); //Father类对象移动到子线程中
thd->start();
...

void MainWindow::on_pushButton_clicked()
{
    qDebug()<<"main pushbutton:"<<QThread::currentThreadId();
    emit f->sig_father();
}

输出:
main pushbutton2: 0x2f94
slot_son 0x909c
slot_father 0x909c

 可以看到,内建对象s在子线程中了,因为内建对象初始化的时候指定了父对象为Father,而Father的线程上下文移动到了子线程中,所以Father的子对象一起也移动到了子线程中。

 

 

 

 

 

posted @ 2023-07-29 10:37  烛爻  阅读(1609)  评论(0编辑  收藏  举报