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 <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 <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();
object tosocket->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:729038198,我们将在24小时内删除。
发表评论