zoukankan      html  css  js  c++  java
  • Qt小技巧9.moveToThread的使用技巧

    1 说下背景

    1.1 常规方式存在的问题

    一般来说,在Qt中使用线程,最常规的做法是继承QThread,重写run函数,调用start函数,run函数里边的代码就会在新的线程中执行了。这样做有点麻烦,要继承、重写,还容易出错,最典型的错误如下:

    QObject: Cannot create children for a parent that is in a different thread.

    这个错误想必所有Qter都犯过,如果你没发过这个错误,请接受我五体投地一拜。这个错误的原因也很简单,run函数是在新的线程中执行,在run函数中实例化对象时入了this参数,但是QThread对象(也就是this)本身是附属于主线程的,他两属于不同的时空的对象,简单来说你在新的线程中创建了一个对象,同时为这个对象指定了一个另一个线程的对象为父对象,这样是不对的,所以会报上面的警告。


    也好解决,一般来说打开线程的事件循环(执行exec()),然后在run函数中创建局部变量(对象)即可。

    1.2 推荐的方式

    QObject提供了moveToThread接口,可以将QObject对象移动到新的线程,此时有个注意点,就是此时与该对象的交互只能通过信号槽的方式了,如果在主线程直接调用该对象函数,那么该函数是不会在新的线程中执行的。虽然moveToThread该接口十分简洁,也推荐使用,但是要想用得好也不是那么容易,下面以一个简单例子来说明。

    2 举一个例子

    2.1 前提

    这里定义一个类MyObject,该类包含一个成员socket,本例子目标是过moveToThread将该类以及成员移动到新的线程。

    2.2 成员变量的方式

    这里直接定义一个QThread成员变量,用于将MyObject移动到新的线程,代码如下:

    #ifndef MYOBJECT_H
    #define MYOBJECT_H
    
    #include <QObject>
    #include <QThread>
    #include <QUdpSocket>
    
    class MyObject : public QObject
    {
        Q_OBJECT
    public:
        explicit MyObject(QObject *parent = 0);
        ~MyObject();
    
    private:
        QThread thread;
        QUdpSocket socket;
    };
    
    #endif // MYOBJECT_H
    
    #include "MyObject.h"
    
    #include <QDebug>
    
    MyObject::MyObject(QObject *parent) : QObject(parent)
    {
        qDebug() << "main thread" << QThread::currentThread();
    
        this->moveToThread(&thread);
        thread.start();
    
        qDebug() << "socket thread" << socket.thread();
        qDebug() << "MyObject thread" << this->QObject::thread();
    }
    
    MyObject::~MyObject()
    {
        thread.quit();
        thread.wait();
    }
    

    打印如下:

    main thread QThread(0x13169c80)
    socket thread QThread(0x13169c80)
    MyObject thread QThread(0x28fe1c)

    貌似和想象中的不一样,socket还是在主线程,我们的目标是也要将它移动到新的线程,这里需要注意,socket作为MyObject的成员对象,并不是MyObject的子对象。而moveToThread的作用是更改此对象及其子对象的线程关联,所以这里并没有什么毛病。要想socket成为MyObject的子对象也好办,使用成员指针的方式。

    2.3 成员指针的方式

    首先修改代码如下:

    #ifndef MYOBJECT_H
    #define MYOBJECT_H
    
    #include <QObject>
    #include <QThread>
    #include <QUdpSocket>
    
    class MyObject : public QObject
    {
        Q_OBJECT
    public:
        explicit MyObject(QObject *parent = 0);
        ~MyObject();
    
    private:
        QThread thread;
        QUdpSocket *socket = nullptr;
    };
    
    #endif // MYOBJECT_H
    
    #include "MyObject.h"
    #include <QDebug>
    
    MyObject::MyObject(QObject *parent) : QObject(parent)
    {
        qDebug() << "main thread" << QThread::currentThread();
    
        socket = new QUdpSocket(this);
    
        this->moveToThread(&thread);
        thread.start();
    
        qDebug() << "socket thread" << socket->thread();
        qDebug() << "MyObject thread" << this->QObject::thread();
    }
    
    MyObject::~MyObject()
    {
        thread.quit();
        thread.wait();
    }
    

    打印如下:

    main thread QThread(0x13279c80)
    socket thread QThread(0x28fe20)
    MyObject thread QThread(0x28fe20)

    现在socket作为MyObject的子对象,成功移动到新的线程了,这里应该很好理解,socket在构造时指定了this(也就是MyObject)作为父对象。

    3 继续找坑

    socket调用下bind,代码如下:

    MyObject::MyObject(QObject *parent) : QObject(parent)
    {
        qDebug() << "main thread" << QThread::currentThread();
    
        socket = new QUdpSocket(this);
    
        this->moveToThread(&thread);
        thread.start();
    
        socket->bind(QHostAddress::Any, 10001);
    
        qDebug() << "socket thread" << socket->thread();
        qDebug() << "MyObject thread" << this->QObject::thread();
    }
    

    输出如下:

    main thread QThread(0x979c80)
    QObject: Cannot create children for a parent that is in a different thread.
    (Parent is QUdpSocket(0x14d95e98), parent's thread is QThread(0x28fe20), current thread is QThread(0x979c80)
    socket thread QThread(0x28fe20)
    MyObject thread QThread(0x28fe20)

    这里也很好理解,经过moveToThread后,socket已经移动到新的线程中了,然而MyObject的构造函数是在主线程中执行的,也就是在主线程中调用了属于另外一个线程的socket的bind函数,bind函数中实例了对象并指定了socket为父对象,也就是在主线程中定义了一个对象,并指定了在另外一个线程的对象为父对象,这样是不对的,怎么办呢?在moveToThread之前bind好就可以了。
    修改代码:

    MyObject::MyObject(QObject *parent) : QObject(parent)
    {
        qDebug() << "main thread" << QThread::currentThread();
    
        socket = new QUdpSocket(this);
        socket->bind(QHostAddress::Any, 10001);
    
        this->moveToThread(&thread);
        thread.start();
    
        qDebug() << "socket thread" << socket->thread();
        qDebug() << "MyObject thread" << this->QObject::thread();
    }
    

    输出如下:

    main thread QThread(0x13339c80)
    socket thread QThread(0x28fe20)
    MyObject thread QThread(0x28fe20)

    好了,一切正常,聪明的你应该已经知道原因了吧。

    4 总结

    QObject::moveToThread的作用是更改此对象及其子对象的线程关联;注意是子对象,并不是成员对象,理解了这个点也就抓住了重点。当然一般做法是在实例对象的地方使用moveToThread,上面的例子是放在了构造函数里面,这样有个好处,对象实例化出来自动就在新的线程中执行了,MyObject构造函数中使用信号槽与socket通信,同时在MyObject外部也使用信号槽的方式进行通信(不能直接调用函数接口,那样还是会在主线程中执行),这样就达到我们的目标了,比起继承QThread重写run函数的方式,这确实要简单多了。

    © 版权声明
    文章版权归作者所有,未经允许请勿转载。【QQ交流:115124903】
    THE END
  • 相关阅读:
    关于加法的类型转换
    设备事件
    html5 事件
    【环境安装】快速安转TensorFlow
    JApiDocs API文档-超级好用
    Docker(超级详细)
    SpringBoot整合Swagger
    Jenkins +Docker+Git 实现自动部署
    Git commit规范
    java支付宝生成二维码
  • 原文地址:https://www.cnblogs.com/luoxiang/p/15343568.html
Copyright © 2011-2022 走看看