重点:怎样正确的使用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>
#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_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,但是在工作线程中处理事件处理并不是一件简单的事。
  • 在事件处理存在时使用对象时将它们移至线程中时很简单的,这样将事件处理和队列连接的细节给隐藏了。

原文:http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-1/

转自:http://blog.csdn.net/zhenwo123/article/details/40861171

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

自我理解(重点):

原则:1、采用信号方式调用通过moveToThread()方法移到另一个线程中运行的对象的槽方法。

    2、不要在该对象A的构造函数中初始化变量,尤其是new新的对象B,因为对象A虽然移到了子线程中,只是代表所有的槽函数在子线程ThreadSon中运行,但是对象A构造的过程是在创建该对象的线程ThreadParent中完成的,也即意味着如果在构造函数中初始化变量或new新对象,则这些变量对象都属于线程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;

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()));
}

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()));

    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);
    commUdpClient->moveToThread(thread);
    thread->start();
}

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

关于: 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的线程
				
时间: 2024-10-09 00:11:28

重点:怎样正确的使用QThread类(注:包括推荐使用QThread线程的新方法QObject::moveToThread)的相关文章

怎样正确的使用QThread类

背景描述: 以前,继承 QThread 重新实现 run() 函数是使用 QThread唯一推荐的使用方法.这是相当直观和易于使用的.但是在工作线程中使用槽机制和Qt事件循环时,一些用户使用错了.Qt  核心开发人员Bradley T. Hughes, 推荐使用QObject::moveToThread 把它们移动到线程中.不幸的是, 以用户反对这样使用.Olivier Goffart, 前Qt  核心开发人之一, 告诉这些用户你们不这样做就错了.最终这俩种用法我们都在QThread的文档中发现

StirngUtil工具类 之 邮箱注冊 域名不区分大写和小写方法

/** * 传入邮箱域名所有变为小写,然后拼接前缀返回 *<b>Summary: </b> * emailDomainTransform() * @param domain * @return * @throws Exception */ public static String emailDomainTransform(String email) throws Exception{ if(email == null || "".equals(email.tri

SWF在线绘本批量制作高质量PDF的新方法(重点在批量制作)

SWF在线绘本批量制作高质量PDF的新方法(重点在批量制作) 2012-12-21  未来决定... http://www.ebama.net/thread-107643-1-1.html 含笑 发表于 2012-12-10 13:51 请教楼主,如何进行压制?是用什么软件吗? 虚拟装载DVD后DVDdcrypter分割提取VOBdgindex制作工程文件和分轨Megui+avs压制mmg封包 具体的教程和软件,TLF小组出过一个打包文件,全在里面了 字幕的话各种情况提取办法不同,图片字幕用su

14 PHP 类的继承 [public protected private] parent 构造方法 析构方法 重写 最终类和方法 设计模式

类的继承 简单理解: 某个类A具有某些特征,另一个类B,也具有A类的所有特征,并且还可能具有自己的更多的一些特征,此时,我们就可以实现:B类使用A的特征信息并继续添加自己的一些特有特征信息. 基本概念 继承:一个类从另一个已有的类获得其特性,称为继承. 派生:从一个已有的类产生一个新的类,称为派生. 继承和派生,其实只是从不同的方向(角度)来表述,本质上就是一个事情. 父类/子类:已有类为父类,新建类为子类.父类也叫"基类",子类也叫"派生类" 单继承:一个类只能从

把jQuery的类、插件封装成seajs的模块的方法

这篇文章主要介绍了把jQuery的类.插件封装成seajs的模块的方法,需要的朋友可以参考下 注:本文使用的seajs版本是2.1.1 一.把Jquery封装成seajs的模块 define(function () { //这里放置jquery代码 把你喜欢的jquery版本放进来就好了 return $.noConflict(); }); 调用方法: 这样引进就可以像以前一样使用jquery define(function (require, exports, module) { var $

mootools1.5.1使用笔记:类的创建,继承,为现有类增加新方法

1 window.addEvent('domready',function(){ 2 /* 3 新建一个Person的类,类上有 name属性和sayHello方法: 4 */ 5 var Person= new Class({ 6 initialize: function(name){ 7 this.name = name; 8 }, 9 sayHello:function(){ 10 console.log('hello,my name is '+this.name); 11 } 12 13

谈谈Runtime类中的freeMemory,totalMemory,maxMemory等几个方法

谈谈Runtime类中的freeMemory,totalMemory,maxMemory等几个方法 Java虚拟机threadJVM 最近在网上看到一些人讨论到java.lang.Runtime类中的freeMemory(),totalMemory(),maxMemory ()这几个方法的一些问题,很多人感到很疑惑,为什么,在java程序刚刚启动起来的时候freeMemory()这个方法返回的只有一两兆字节,而随着 java程序往前运行,创建了不少的对象,freeMemory()这个方法的返回有

Java基础-接口.编写2个接口:InterfaceA和InterfaceB;在接口InterfaceA中有个方法void printCapitalLetter();在接口InterfaceB中有个方法void printLowercaseLetter();然 后写一个类Print实现接口InterfaceA和InterfaceB,要求 方法 实现输出大写英文字母表的功能,printLowerca

#34.编写2个接口:InterfaceA和InterfaceB:在接口InterfaceA中有个方法void printCapitalLetter():在接口InterfaceB中有个方法void printLowercaseLetter():然 后写一个类Print实现接口InterfaceA和InterfaceB,要求      方法 实现输出大写英文字母表的功能,printLowercaseLetter()方法实现输出小写英文 字母表的功能.再写一个主类E,在主类E的main方法中创建P

Java基础-继承-编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight。小车类Car是Vehicle的子类,其中包含的属性有载人数 loader。卡车类Truck是Car类的子类,其中包含的属性有载重量payload。每个 类都有构造方法和输出相关数据的方法。最后,写一个测试类来测试这些类的功 能。

#29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类Truck是Car类的子类,其中包含的属性有载重量payload.每个 类都有构造方法和输出相关数据的方法.最后,写一个测试类来测试这些类的功 能. package hanqi; public class Vehicle { private int wheels; private int weight