重點:怎樣正確的使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM