重点:怎样正确的使用QThread类(注:包括推荐使用QThread线程的新方法QObject::moveToThread)...
背景描述:
以前,继承 QThread 重新实现 run() 函数是使用 QThread唯一推荐的使用方法。这是相当直观和易于使用的。但是在工作线程中使用槽机制和Qt事件循环时,一些用户使用错了。Qt 核心开发人员Bradley T. Hughes, 推荐使用QObject::moveToThread 把它们移动到线程中。不幸的是, 以用户反对这样使用。Olivier Goffart, 前Qt 核心开发人之一, 告诉这些用户你们不这样做就错了。最终这俩种用法我们都在QThread的文档中发现 。
QThread::run() 是线程的入口点
从Qt文档中我们可以看到以下内容:
A QThread instance represents a thread and provides the means to start() a thread, which will then execute the reimplementation of QThread::run(). The run() implementation is for a thread what the main() entry point is for the application.
Usage 1-0
在新的线程中执行一些代码,继承QThread 重新实现 run()函数接口。
For example
#include <QtCore>class Thread : public QThread { private:void run(){qDebug()<<"From worker thread: "<<currentThreadId();} };int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug()<<"From main thread: "<<QThread::currentThreadId();Thread t;QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));t.start();return a.exec(); }
结果输出如下:
From main thread: 0x15a8
From worker thread: 0x128c
Usage 1-1
正因QThread::run() 是线程的入口, 所以很容易的理解它们, 并不是所有的代码都在run()接口中被直接调用而不在工作线程中被执行。
接下来的例子中,成员变量 m_stop
在 stop() 和 run()都可被访问到。考虑到前者将在主线程执行,后者在工作线程执行,互斥锁或其它操作是有必要的。
#if QT_VERSION>=0x050000 #include <QtWidgets> #else #include <QtGui> #endifclass Thread : public QThread {Q_OBJECTpublic:Thread():m_stop(false){}public slots:void stop(){qDebug()<<"Thread::stop called from main thread: "<<currentThreadId();QMutexLocker locker(&m_mutex);m_stop=true;}private:QMutex m_mutex;bool m_stop;void run(){qDebug()<<"From worker thread: "<<currentThreadId();while (1) {{QMutexLocker locker(&m_mutex);if (m_stop) break;}msleep(10);}} };#include "main.moc" int main(int argc, char *argv[]) {QApplication a(argc, argv);qDebug()<<"From main thread: "<<QThread::currentThreadId();QPushButton btn("Stop Thread");Thread t;QObject::connect(&btn, SIGNAL(clicked()), &t, SLOT(stop()));QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));t.start();btn.show();return a.exec(); }
结果输出如下:
From main thread: 0x13a8
From worker thread: 0xab8
Thread::stop called from main thread: 0x13a8
你可以看到Thread::stop() 函数是在主线程中被执行的。
Usage 1-2 (错误的使用方式)
以上的例子很容易明白,但它不是那么直观当事件系统(或队列)中引进工作线程时。
例子如下, 我们应该做些什么,如果我们想在工作线程中周期性的做些动作?
- 在Thread::run()中创建一个QTimer
- 将超时信号连接到线程中的槽函数上
#include <QtCore>class Thread : public QThread {Q_OBJECT private slots:void onTimeout(){qDebug()<<"Thread::onTimeout get called from? : "<<QThread::currentThreadId();}private:void run(){qDebug()<<"From worker thread: "<<currentThreadId();QTimer timer;connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()));timer.start(1000);exec();} };#include "main.moc"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug()<<"From main thread: "<<QThread::currentThreadId();Thread t;t.start();return a.exec(); }
乍看起来代码没什么问题。当线程开始执行时, 我们在当前线程的事件处理中启动了一个定时器。我们将 onTimeout()
连接到超时信号上。此时我们希望它在线程中执行?
但是执行结果如下:
From main thread: 0x13a4
From worker thread: 0x1330
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4
Oh, No!!! 它为什么在主线程中被调用了!
是不是很有趣?(接下来我们将要讨论这是为什么)
如何解决这个问题
为了使槽函数工作在线程中, 有人尝试在connect()函数中传入参数 Qt::DirectConnection
connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);
还有人尝试在线程构造函数中添加如下功能。
moveToThread(this)
它们都会如期望的工作吗. 但是 …
第二个用法的是错误的,
尽管看起来它工作啦,但很令人费解,这不是QThread 设计的本意(QThread 中所有实现的函数是被创建它的线程来调用的,不是在线程中)
实际上,根据以上表述,第一个方案是错误的。onTimeout() 是线程对象的一个成员函数,会被创建它的线程来调用。
它们都是错误的使用方法?!我们该如何做呢?
Usage 1-3
因为没有一个线程类的成员是设计来被该线程调用的。所以如果我们想使用槽函数必须创建一个独立的工作对象。
#include <QtCore>class Worker : public QObject {Q_OBJECT private slots:void onTimeout(){qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();} };class Thread : public QThread {Q_OBJECTprivate:void run(){qDebug()<<"From work thread: "<<currentThreadId();QTimer timer;Worker worker;connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));timer.start(1000);exec();} };#include "main.moc"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug()<<"From main thread: "<<QThread::currentThreadId();Thread t;t.start();return a.exec(); }
结果如下:
From main thread: 0x810
From work thread: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac
问题解决啦!
尽管运行的很好,但是你会注意到,当工作线程中运行在事件循环 QThread::exec()
中时,在QThread::run() 函数接口中没有执行自身相关的事务。
所以我们是否可以将对象的创建从QThread::run()中移出, 此时, 槽函数是否依旧会被QThread::run()调用?
Usage 2-0
如果我们只想使用QThread::exec(), 默认情况下会被QThread::run() 调用, 那不需要子类化QThread。
- 创建一个工作对象
- 建立信号与槽的连接
- 将工作对象移至子线程中
- 启动线程
#include <QtCore>#include <QUdpSocket>class Worker : public QObject {Q_OBJECTpublic: void Work() { }private: QUdpSocket *socket; private slots:void onTimeout(){qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();} };#include "main.moc"int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug()<<"From main thread: "<<QThread::currentThreadId();QThread t;QTimer timer;Worker worker;QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));timer.start(1000);timer.moveToThread(&t);worker.moveToThread(&t);t.start();return a.exec(); }
结果是:
From main thread: 0x1310
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c
正如所预料的,槽函数没有在主线程中执行。
在此例中,定时器和工作对象都被移至子线程中,实际上,将定时器移至子线程中是没有必要的。
Usage 2-1
在上面的例子当中将 timer.moveToThread(&t);
这一行注释掉。
int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);qDebug()<<"From main thread: "<<QThread::currentThreadId();QThread t;QTimer timer;Worker worker;QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));timer.start(1000);// timer.moveToThread(&t);worker.moveToThread(&t);t.start();return a.exec(); }
不同之处如下:
在上面的例子中,
- 信号
timeout()
是由子线程发送出去的。 - 定时器和工作对象是在同一线程当中的,它们的连接方式直接连接。
- 槽函数的调用和信号的触发是在同一线程中的。
在本例中,
- 信号
timeout()
是由主线程发出的。 - 定时器和工作对象是在不同的线程中,它们的连接方式队列连接。
- 槽函数是在子线程中被调用。
- 由于队列连接机制的存在,可以安全的将信号和槽函数在不同的线程中连接起来。如果所有的跨线程通信都通过队列连接方式,那么多线程的互斥防范机制将不在需要。
总结
- 子类化QThread ,实现 run()函数接口是很直观的,也存在很多不错的方法去子类化QThread,但是在工作线程中处理事件处理并不是一件简单的事。
- 在事件处理存在时使用对象时将它们移至线程中时很简单的,这样将事件处理和队列连接的细节给隐藏了。
QObject::thread()
可以查询一个QObject
的线程依附性,通过QThread::currentThread()可以查询当前运行所属线程,都非常有用。
QThread *thread; thread=new QThread(this); qDebug()<<"1:"<<commUdpClient->thread(); commUdpClient->moveToThread(thread); thread->start(); qDebug()<<"2:"<<commUdpClient->thread();
输出:
说明对象commUdpClient确实发生所在线程的变化。但是值得注意的是,commUdpClient对象中的
QUdpSocket *socket; QUdpSocket *socket2; QUdpSocket socket3; 前两个指针在构造函数里和槽函数里初始化,所属线程是有很大区别的。如下面代码所示,在构造函数里初始化socket2,意味着属于主线程,不管commUdpClient是否移到子线程,它还是属于主线程;而socket是在commUdpClient对象移到子线程后,再在槽函数中初始化,则属于子线程;socket3默认在构造函数里初始化,即使构造函数没有显式初始化,但是也属于主线程,其它如int、char等基础数据变量也是属于主线程的。所以在利用moveToThread方式使用多线程的情况下,commUdpClient类声明中类类型对象建议都声明为指针,然后都在某个槽函数中初始化,而基础数据变量如int、char等建议声明为private,如果需要改变则利用属性的方式get、set来改变,实在需要使用public,则需要加锁,不然会出错。
所以要求A的初始化过程放到某个槽函数中进行,包括connect的过程,然后由线程ThreadParent中调用A的对象C通过信号的方式触发该槽。A其它的槽要想在对象C中调用也需要通过信号的方式,如果通过直接调用A.XX()方式调用槽函数或者其它函数,则仍旧是在线程ThreadParent中运行,非在线程ThreadSon中运行。切记切记,具体看以下代码:
对象B的源码.h
//CommUdpClient.h#include <QObject> #include <QUdpSocket>class CommUdpClient : public QObject {Q_OBJECT public:explicit CommUdpClient(QObject *parent = 0);~CommUdpClient();QUdpSocket *socket; QUdpSocket *socket2; QUdpSocket socket3;private:QHostAddress serverAddress;quint16 serverPort;public://发送报文void send(const QByteArray &msg);signals://接收完报文后触发的信号,用于上层触发相应的槽,获得报文void sigRevedMsg(QByteArray msg,const QStringList ¶s);public slots://报文发送完成信号bytesWritten对应的槽void sltSent(qint64 bytes);//接收报文,对应信号readyRead的槽 QByteArray sltReceive();//通讯参数设置,打开连接void sltOpenConnect(const QStringList ¶s);};
对象B的源码.CPP
#include "commudpclient.h" #include <qstringlist.h> #include <QMetaEnum> #include <QDebug> #include <QThread>CommUdpClient::CommUdpClient(QObject *parent) :QObject(parent) {qDebug()<<"udp current thread 1:"<<QThread::currentThread();//错误//socket=new QUdpSocket;//connect(socket,SIGNAL(bytesWritten(qint64)),this,SLOT(sltSent(qint64)));//connect(socket,SIGNAL(readyRead()),this,SLOT(sltReceive())); socket2=new QUdpSocket; qDebug()<<"socket2 thread:"<<socket2->thread(); }CommUdpClient::~CommUdpClient() {if(socket!=NULL){if(socket->isOpen()){socket->close();}delete socket;socket=NULL;} }void CommUdpClient::sltOpenConnect(const QStringList ¶s) {qDebug()<<"udp current thread 2:"<<QThread::currentThread();//正确socket=new QUdpSocket();//这里加this和不加this是有区别的这是打印出来的socket的parent,是有区别的,不过这里的CommUdpClient也是顶层的,无parent的,不然是无法移动到子线程中的,会报错,具体在对象B的源码中描述。
connect(socket,SIGNAL(bytesWritten(qint64)),this,SLOT(sltSent(qint64)));connect(socket,SIGNAL(readyRead()),this,SLOT(sltReceive())); //测试 qDebug()<<"socket thread:"<<socket->thread(); qDebug()<<"socket2 thread:"<<socket2->thread(); qDebug()<<"scoket3 thread:"<<socket3.thread(); qDebug()<<this<<", thread:"<<this->thread(); serverAddress.setAddress(paras[0]);serverPort=paras[1].toUShort();socket->bind(paras[2].toUShort()); }void CommUdpClient::send(const QByteArray &msg) { qDebug()<<"udp send thread:"<<QThread::currentThread();if(!serverAddress.isNull()){socket->writeDatagram(msg,serverAddress,serverPort);qDebug()<<"client send end";} }void CommUdpClient::sltSent(qint64 bytes) {}QByteArray CommUdpClient::sltReceive() {qDebug()<<"server start receive...";while (socket->hasPendingDatagrams()) {QByteArray datagram;datagram.resize(socket->pendingDatagramSize());QHostAddress sender;quint16 senderPort;socket->readDatagram(datagram.data(), datagram.size(),&sender, &senderPort);qDebug()<<datagram;QStringList sl;sl.append(sender.toString());sl.append(QString::number(senderPort));emit sigRevedMsg(datagram,sl);return datagram;} }
对象A的源码.h
#ifndef COMMUNICATION_H #define COMMUNICATION_H#include <QObject> #include <QThread> #include <qstringlist.h> #include "commtcpclient.h"class Communication : public QObject {Q_OBJECT public:explicit Communication(QObject *parent = 0);virtual ~Communication();public:CommUdpClient *commUdpClient;QThread *thread;public:void readData();void writeData();signals:void sigThreadConnect(const QStringList ¶s);public slots:void sltRevedMsg(QByteArray msg,const QStringList ¶s);};#endif // COMMUNICATION_H
对象B的源码.cpp
#include "communication.h" #include <QCoreApplication> #include <QDebug> #include <QTime>Communication::Communication(QObject *parent) :QObject(parent) {qDebug()<<"current thread:"<<QThread::currentThread();commUdpClient=new CommUdpClient();//这里不能加this,不然是不允许移到子线程中,报错
connect(commUdpClient,SIGNAL(sigRevedMsg(QByteArray,QStringList)),this,SLOT(sltRevedMsg(QByteArray,QStringList)));connect(this,SIGNAL(sigThreadConnect(QStringList)),commUdpClient,SLOT(sltOpenConnect(QStringList)));thread=new QThread(this); qDebug()<<"1:"<<commUdpClient->thread();commUdpClient->moveToThread(thread); qDebug()<<"2:"<<commUdpClient->thread(); thread->start(); qDebug()<<"3:"<<commUdpClient->thread(); }Communication::~Communication() {delete commUdpClient;thread->quit();delete thread; }void Communication::readData() {QString sz="192.168.1.186:1234:1235";QStringList sl=sz.split(':');emit sigThreadConnect(sl);//正确用法
//commUdpClient->send("error");//不正确的用法,这个是直接在对象B的线程中运行的
}void Communication::writeData() {qDebug()<<"xxxx"; }void Communication::sltRevedMsg(QByteArray msg) {qDebug()<<"communication end,receive msg:"<<msg; }void Communication::sltRevedMsg(QByteArray msg, const QStringList ¶s) { }
远行结果:
//commUdpClient->send("error");//不正确的用法,这个是直接在对象B的线程中运行的
具体的参考资料:http://blog.csdn.net/sydnash/article/details/7425947 一种使用QThread线程的新方法QObject::moveToThread
http://blog.csdn.net/lainegates/article/details/9701215
http://mobile.51cto.com/symbian-268690_1.htm
关于: QObject: Cannot create children for a parent that is in a different thread错误
参考:http://blog.chinaunix.net/uid-26808060-id-3355816.html
class TcpComm:public QThread {Q_OBJECT public:TcpComm(const QString &iAddrStr, quint16 iPort);~TcpComm();........ private: .......TcpClient*mTcpClient; };TcpComm::TcpComm(const QString &iAddrStr, quint16 iPort):mAddr(iAddrStr), mPort(iPort) {mIsStop = false;mTcpClient = new TcpClient();start(); } 以上程序在运行时报QObject: Cannot create children for a parent that is in a different thread错误。 将原构造函数中的mTcpClient = new TcpClient();放到run()中问题解决。 TcpComm::TcpComm(const QString &iAddrStr, quint16 iPort):mAddr(iAddrStr), mPort(iPort) {mIsStop = false;start(); }void TcpComm::run() {mTcpClient = new TcpClient();........ } 查了查,原因应该是,在QThread中定义的所有东西都属于创建该QThread的线程。所以在构造函数中初始化的mTcpClient应该是属于父线程的,那么在run中调用时就属于跨线程调用。所以把mTcpClient放到run中初始化就属于线程的了,调用时就不会出现跨线程调用的问题。另外:QThread中写的所有函数都应该在创建它的线程中调用,而不是开启QThread的线程
其它参考资料:http://blog.csdn.net/zhangbinsijifeng/article/details/52299926 qt中的线程 拥有权 一个对象属于哪个线程
http://blog.csdn.net/an505479313/article/details/50351745 QThread使用——关于run和movetoThread的区别
http://blog.csdn.net/dbzhang800/article/details/6554104 Qt 线程基础(QThread、QtConcurrent等)
转载于:https://www.cnblogs.com/liushui-sky/p/5829563.html
重点:怎样正确的使用QThread类(注:包括推荐使用QThread线程的新方法QObject::moveToThread)...相关推荐
- 复旦大学类脑智能研究院发展电刺激伪迹实时处理新方法,为智能闭环神经调控提供关键技术...
闭环电刺激已经显示出了其在运动障碍疾病调控方面的优势,相对于持续电刺激治疗,闭环电刺激以更小的刺激剂量取得了更好的症状改善和更低的刺激副作用. 但是近10年来,关于闭环电刺激策略的研究仍然更多停留在计 ...
- Qt修炼手册11_多线程编程和QThread类
1.事件循环 学习QT多线程编程之前,有必要先熟悉事件循环的概念. 先看一个单线程界面程序的主函数代码: int main(int argc, char* argv[]) {QApplication ...
- QT的QThread类的使用
详细说明 QThread类提供了一种独立于平台的方式来管理线程. 一个QThread对象管理程序中的一个控制线程. QThreads开始在run()中执行. 默认情况下,run()通过调用exec() ...
- Python|装饰器|执行时间|递归|动态属性|静态方法和类|继承和多态|isinstance类型判断|溢出|“魔法”方法|语言基础50课:学习记录(6)-函数的高级应用、面向对象编程、进阶及应用
文章目录 系列目录 原项目地址: 第16课:函数的高级应用 装饰器(记录执行时间的例子) 递归调用 简单的总结 第17课:面向对象编程入门 类和对象 定义类 创建和使用对象 初始化方法 打印对象 面向 ...
- C++中访问类的私有数据成员的第三种方法
我们知道,C++的类是有封装性的,那么对于私有数据成员我们如果想在类外访问,一般而言无外乎这么两种方法: 1.通过公有的成员函数 2.通过友元 这是两种通常的做法,还有一种是比较"反常&qu ...
- php protected 的继承,14 PHP 类的继承 [public protected private] parent 构造方法 析构方法 重写 最终类和方法 设计模式...
类的继承 简单理解: 某个类A具有某些特征,另一个类B,也具有A类的所有特征,并且还可能具有自己的更多的一些特征,此时,我们就可以实现:B类使用A的特征信息并继续添加自己的一些特有特征信息. 基本概念 ...
- java为什么不推荐使用stack_栈和队列的面试题Java实现,Stack类继承于Vector这两个类都不推荐使用...
在 thinking in java中看到过说Stack类继承于Vector,而这两个类都不推荐使用了,但是在做一到OJ题时,我用LinkedList来模拟栈和直接用Stack,发现在进行入栈出栈操作 ...
- account表里有什么 银行_模拟一个银行账户类Account,账户类中包括所有者、账号、余额、账户总数、存款、取款等信息。_学小易找答案...
[单选题]廉价磁盘冗余阵列RAID利用冗余技术实现高可靠性,其中RAID1的磁盘利用率为() . [简答题]与直流调速相比,交流调速有何优点? [论述题]方法 [单选题]以<老子注>一书驰 ...
- 评估模型都有哪些_BIM模型主要分为哪几类模型?包括哪些内容?
来源:中国BIM培训网 BIM不是由某个独立参与者管理和使用的单一.独立模型,BIM应该是彼此不同又互相联系的子模型,一般的划分方法是将它们分为建筑模型.结构模型和机电模型.今天我们就聊聊BIM模型主 ...
最新文章
- 【2021Java最新学习路线】kvm和docker区别
- 【CyberSecurityLearning 附】批处理命令拓展(netsh/netstat/net)
- 创作共用协议创始人-Lawrence Lessig(2)
- flutter刷新页面_用Flutter实现58App的首页
- Spring系列之Bean生命周期
- 多维随机变量与其对应的分布
- 学生管理系统(VB)——连接数据库
- 五类推荐系统算法,非常好使,非常全面
- 【Tools】Photoshop CS6安装详解教程
- centos7下dos界面图形界面切换学习
- MOOS-ivp 实验五 MOOS编程进阶(3)
- 打印zigtag矩阵
- java编写机器人_用JAVA编写自己的机器人一起“厮杀”
- Selenium自动化测试工具的介绍与使用
- SAP SD 客户物料主数据
- EOS智能合约开发系列(17): 神秘的eosio.code
- Thread.currentThread()与this的区别
- 2017CCF大数据学术会议大数据智能分析分论坛成功举办
- Android系统proc下查看cpuinfo的参数信息
- 【linked-java】369.Plus One Linked List
热门文章
- python有什么功能-Python 3.9有什么新功能?
- 计算机二级python用什么书-如何准备全国计算机二级Python?
- python读取excel-Python Pandas读取修改excel操作攻略
- python画三维立体图-python_matplotlib画三维图
- python画动图-利用Python如何制作好玩的GIF动图详解
- python导入txt为dataframe-python读取文本中数据并转化为DataFrame的实例
- java和python的web自动化有什么区别-三分钟看懂Python和Java的区别
- python英语翻译-python制作英语翻译小工具
- 以下不是python文件读写方法的是-Python 文件I/O
- python花钱培训值吗-Python培训费用高不高?Python培训真的值得吗?