zoukankan      html  css  js  c++  java
  • 重点:怎样正确的使用QThread类(很多详细例子的对比,注意:QThread 中所有实现的函数是被创建它的线程来调用的,不是在线程中)good

    背景描述:

    以前,继承 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>
    #endif
    
    class Thread : public QThread
    {
        Q_OBJECT
    
    public:
        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_OBJECT
    
    private:
        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_OBJECT
    public:
      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,但是在工作线程中处理事件处理并不是一件简单的事。
    • 在事件处理存在时使用对象时将它们移至线程中时很简单的,这样将事件处理和队列连接的细节给隐藏了。
     
     
    转自:http://blog.csdn.net/zhenwo123/article/details/40861171
     
     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     
    自我理解(重点):
    理解:如QThread thread; A.moveToThread(&thread),moveToThread() 是改变了对象A的线程依附性关系。将对象A及其内部的成员变量的所属线程切换到次线程thread中。注:可以通过调用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,则需要加锁,不然会出错。 
     
    原则:1、采用信号方式调用通过moveToThread()方法移到另一个线程中运行的对象的槽方法。
        2、不要在该对象A的构造函数中初始化指针变量B(如B=new QUdpSocket()),因为对象A虽然移到了子线程中,只是代表所有的槽函数在子线程ThreadSon中运行,但是对象A构造的过程是在创建该对象的线程ThreadParent中完成的,也即意味着如果在构造函数中初始化指针变量,则该指针指向的内存还是属于线程ThreadParent,而非线程ThreadSon,这与我们的初衷不相符合,尤其是这些对象要在槽函数中使用,会报错如:

    所以要求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 &paras); public slots: //报文发送完成信号bytesWritten对应的槽 void sltSent(qint64 bytes); //接收报文,对应信号readyRead的槽 QByteArray sltReceive(); //通讯参数设置,打开连接 void sltOpenConnect(const QStringList &paras); };
    复制代码

     对象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 &paras) { 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 &paras);
    
    public slots:
         void sltRevedMsg(QByteArray msg,const QStringList &paras);
    
    };
    
    #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 &paras)
    {
    
    }
    复制代码

    远行结果:

     //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?tdsourcetag=s_pcqq_aiomsg

  • 相关阅读:
    POJ 题目2750 Potted Flower(线段树求环型区间中连续区间的最大和)
    即使没人看我们也要坚持写Blog
    鸟哥的Linux私房菜-----7、硬件管理
    android createbitmap函数内存溢出,求解怎样进行处理out of memory溢出问题
    hdu4614Vases and Flowers 线段树
    安装npm及cnpm(Windows)
    安装npm及cnpm(Windows)
    安装npm及cnpm(Windows)
    Echarts设置点击事件
    Vs Code中炫酷写代码插件Power Mode的安装配置
  • 原文地址:https://www.cnblogs.com/findumars/p/10301209.html
Copyright © 2011-2022 走看看