1. 程式人生 > >QThread 與 QObject的關係(QObject可以用於多執行緒,可以傳送訊號呼叫存在於其他執行緒的slot函式,但GUI類不可重入)

QThread 與 QObject的關係(QObject可以用於多執行緒,可以傳送訊號呼叫存在於其他執行緒的slot函式,但GUI類不可重入)

QThread 繼承 QObject.。它可以傳送started和finished訊號,也提供了一些slot函式。

QObject.可以用於多執行緒,可以傳送訊號呼叫存在於其他執行緒的slot函式,也可以postevent給其他執行緒中的物件。之所以可以這樣做,是因為每個執行緒都有自己的事件迴圈。

在進行下面的講解之前,應該瞭解的重要的一點是:QThread 物件所在的執行緒,和QThread 建立的執行緒,也就是run()函式執行的執行緒不是同一個執行緒。QThread 物件所在的執行緒,就是建立物件的執行緒。我們通過一個例子說明更能清楚一點:

MyThread::MyThread(QObject *parent /* = NULL */):QThread(parent)

{

       qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();

}

 

void MyThread::run()

{

       qDebug()<<"run()  currentThreadId : "<<QThread::currentThreadId();

}

 

int main(int argc, char *argv[])

{

       QApplication a(argc, argv);

       MyThread thread;

       qDebug()<<"mainThread : "<<QThread::currentThreadId();

       thread.start();

       returna.exec();

}

 

輸出結果:MyThread所在的執行緒就是主執行緒,run()函式是新開的執行緒。

QObject Reentrancy

QObject.是可重入的,它的大多數非GUI子類,例如QTimerQTcpSocketQUdpSocket and QProcess都是可重入的,使得這些類可以同時用於多執行緒。需要注意的是,這些類設計為從一個單一的執行緒建立和使用的,在一個執行緒建立物件,而從另外一個執行緒呼叫物件的函式並不能保證行得通。有三個限制需要注意:

1.    QObject的子物件必須在建立其parent的執行緒中建立。這意味著,你不能把QThread物件作為parent傳遞給建立線上程中的物件,因為QThread 物件本身在另外一個執行緒中建立。

2.    事件驅動物件只能用於單執行緒。尤其是在定時器機制和網路模組。例如,你不能在不是物件所處的執行緒start一個計時器或者連結一個secket。簡單的說就是,你不能線上程A建立了一個計時器timer,然後線上程B從啟動timer。

我們可以驗證一下:

class MyThread : publicQThread

{

       Q_OBJECT

public:

       MyThread(QObject *parent = NULL);

       ~MyThread();

 

public slots:

       voidtimeOutSlot();

protected:

       voidrun();

       QTimer *m_pTimer;

 

};

 

MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)

{

       m_pTimer = newQTimer(this);

       qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();

       connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));

}

 

void MyThread::timeOutSlot()

{

       qDebug()<<"timer  timeout ";

}

 

MyThread::~MyThread()

{

 

}

 

void MyThread::run()

{

       m_pTimer->start(500);

       qDebug()<<"run()  currentThreadId : "<<QThread::currentThreadId();

       qDebug( "finish!");

}

 

 

 

 intmain(int argc, char*argv[])

{

       QApplication a(argc, argv);

       MyThread thread;

       qDebug()<<"mainThread : "<<QThread::currentThreadId();

       thread.start();

       returna.exec();

}

 

Timeout函式並沒有被呼叫。我們還發現有多了一行輸出:QObject::startTimer: timers cannot be startedfrom another thread

跟蹤timer的start原始碼,我們發現:

void QEventDispatcherWin32::registerTimer(int timerId, intinterval, QObject *object)

{

    if (timerId< 1 || interval < 0 || !object) {

        qWarning("QEventDispatcherWin32::registerTimer:invalid arguments");

        return;

}

 else if(object->thread() != thread() || thread() != QThread::currentThread())

{

//判斷object的thread,也就是object所在的thread,不等於當前的執行緒就返回了

        qWarning("QObject::startTimer:timers cannot be started from another thread");

        return;

    }

 

  。。。。。

 

}

 

3.    你必須保證線上程中建立的物件要線上程銷燬前delete。這很容易做到,只要是在run()函式棧裡建立的物件就行。

儘管 QObject 是可重入的,但是GUI類,特別是QWidget 和它的子類都是不可重入的。它們只能在主執行緒中用。就如前面提到的, QCoreApplication::exec()必須從主執行緒進行呼叫。

 

Per-Thread Event Loop

每個執行緒都有自己的事件迴圈。起始的執行緒用QCoreApplication::exec()開啟事件迴圈。其他的執行緒用QThread::exec()開始事件迴圈。與 QCoreApplication一樣, QThread也提供了 exit(int) 函式 和 quit() 槽函式。

執行緒裡的事件迴圈,使得可以線上程裡使用需要事件迴圈的非GUI類,例如(QTimerQTcpSocket, and QProcess).。也可以把任意的執行緒的訊號連線到特定執行緒的槽。

QObject例項存在於建立例項的執行緒中,傳送給例項事件也是有執行緒的事件迴圈實現的。可以用 QObject::thread().獲取物件存活於哪個執行緒。

MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)

{

       m_pTimer = newQTimer(this);

       qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThread();

       QObject obj1;

       obj1.thread();

       qDebug()<<"obj1live in the thread  :"<<obj1.thread();

       connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));

       //QThread::start();

}

 

 

void MyThread::run()

{

       QObject obj2;

       obj2.thread();

       qDebug()<<"button2live in the thread  :"<<obj2.thread();

       //m_pTimer->start(500);

       qDebug()<<"run()  currentThreadId : "<<QThread::currentThread();

       qDebug( "finish!");

}

 

這個再一次說明了,物件所處的執行緒就是建立它的執行緒。

 

注意:對於那些在QApplication之前建立的物件,QObject::thread() 返回0。這意味著,主執行緒只處理髮送給那些物件的事件,那些沒有thread的物件是不做任何的事件處理的。使用QObject::moveToThread()函式可以改變物件及其子物件的執行緒關聯度,說白了就是把物件從當前的執行緒移到另外的執行緒裡。但是如果一個物件已經有了parent,那是不能move了。

呼叫delete刪除處於另外一個執行緒的QObject物件是不安全的。除非你能保證物件當前不是在進行事件處理。應該用QObject::deleteLater()替代,並且將發出一個DeferredDelete事件,這個事件會最終會被物件的執行緒的時間迴圈所捕獲。

如果沒有時間迴圈,就不會有事件傳遞給物件。例如,如果你在一個執行緒中建立了一個QTimer物件,但是不呼叫exec(),,那麼QTimer永遠不會發出timeout()訊號,呼叫eleteLater() 也不起作用。

void MyThread::run()

{

       m_pTimer = newQTimer();

       m_pTimer->start(500);

      

       connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));

       qDebug()<<"run()  currentThreadId : "<<QThread::currentThread();

       this->exec();

       //qDebug("finish!" );

}

void MyThread::timeOutSlot()

{

       qDebug()<<"timer  timeout ";

       //m_pTimer->stop();

}

 

這時候是可以呼叫timeOutSlot()的。

 

 

void MyThread::run()

{

       m_pTimer = newQTimer();

       m_pTimer->start(500);

      

       connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));

       qDebug()<<"run()  currentThreadId : "<<QThread::currentThread();

       //this->exec();

       //qDebug("finish!" );

}

如果註釋//this->exec();,timeOutSlot()將不會被呼叫。

還有一點要注意的:QTimer對 象也不能在另外的執行緒stop的。如果把timeOutSlot裡的m_pTimer->stop();取消註釋。會看到一行輸 出:QObject::killTimer: timers cannot be stopped fromanother thread

原始碼中:

bool QEventDispatcherWin32::unregisterTimer(int timerId)

{

    if (timerId< 1) {

        qWarning("QEventDispatcherWin32::unregisterTimer:invalid argument");

        return false;

    }

QThread *currentThread = QThread::currentThread();

//判斷timer所處的執行緒與當前的執行緒是否一致。

if(thread() != currentThread)

 {

        qWarning("QObject::killTimer:timers cannot be stopped from another thread");

        return false;

    }

 

  。。。。

}

你可以用QCoreApplication::postEvent()函式在任意時間給任意執行緒中的任意物件傳送事件。事件自動被建立object的執行緒的事件迴圈分發。所以的執行緒都支援事件過濾器,唯一的限制就是,監視物件必須與被監視物件處於同一個執行緒。同樣的,QCoreApplication::sendEvent() 只能用來給與呼叫QCoreApplication::sendEvent() 函式處於同一個執行緒的物件傳送事件。說白了就是,QCoreApplication::sendEvent() 不能給處於另外執行緒的物件傳送事件。

 

Accessing QObjectSubclasses from Other Threads

QObject 和它所有的子類都不是執行緒安全的。這包含了整個事件傳送系統,需要記住的很重要的一點是:事件迴圈可能正在給一個物件傳送一個事件,同時你可能從別的執行緒訪問該物件。

如果你呼叫了一個不是出於當前執行緒QObject 子類物件的一個函式,而此時物件可能接收一個事件,你必須用一個mutex保護物件的內在的資料。否則,可能引起程式崩潰或者未定義的行為。

與其他的物件一樣,QThread物件存活於建立物件的執行緒中,而不是存在於QThread::run() 執行緒。這點在前面講到了。在自定義 QThread子類中提供slot函式是不安全的,除非你用一個mutex保護了成員變數。然而,你可以在實現的 QThread::run() 裡發出訊號,因為訊號傳送是執行緒安全的。

Signals and Slots AcrossThreads

Qt支援了幾種訊號--槽的連線方式:

1.     Auto Connection (預設):如果如果訊號的傳送方與接收方是處於同一個執行緒,這個連線就是 Direct Connection,否則就跟 Queued Connection一樣。

2.    Direct Connection :當訊號發出之後,槽會立即被呼叫。槽函式是在訊號傳送方的執行緒中執行的,不需要接收方的執行緒。

3.    Queued Connection:當控制權回到接收方執行緒時呼叫槽函式。槽函式是在接收方的執行緒中執行的。

4.    Blocking Queued Connection :呼叫方式跟 Queued Connection一樣,區別在於,當前執行緒會被阻塞直到槽函式返回。

5.    Unique Connection :這種方式跟 Auto Connection一樣,但是隻有當不存在一個相同的連線時才會建立一個連線。如果已經存在相同的連線,則不會建立連線,connect()返回false。

可以在connect()新增引數指定連線型別。需要注意的一點是:如果訊號傳送方和接收方處於不同的執行緒,而且接收方執行緒執行著一個事件迴圈,此時用Direct Connection是不安全,原因跟呼叫一個物件的函式,而這個物件處於另外的執行緒,那樣的呼叫是不安全。

QObject::connect() 本身是執行緒安全的。

下面通過結果例子驗證一下:

 

class Receiver:publicQObject

{

       Q_OBJECT

public:

 

       voidsendmes()

       {

              emitemitSignal("emit message from A  To B");

       }

       Receiver()

       {

 

       }

protected slots:

       voidmessageSlot(QString mes)

       {

              qDebug()<<mes;

       }

signals:

       voidemitSignal(QString mes);

 

private:

};

int main(int argc, char *argv[])

{

       QApplication a(argc, argv);

       Receiver objA,objB;

      

       QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));

       qDebug()<<"beforeemitsignal ";

       objA.sendmes();

       qDebug()<<"afteremitsignal ";

       returna.exec();

}

objA,objB;出於同一個執行緒,所以connect的連線型別是Direct Connection

由輸出我們可以看出執行順序,

 

如果我們寫了兩句連線:

QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));

       QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));

 

就會相應的有兩句訊息輸出:

 

如果指定了連線型別Qt::UniqueConnection ,就會只有一句訊息輸出了。

QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection );

QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection);

 

int main(int argc, char *argv[])

{

       QApplication a(argc, argv);

       QThread *thread = new QThread;

       thread->start();

       Receiver objA,objB;

       objB.moveToThread(thread);

       QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) );

       qDebug()<<"beforeemitsignal ";

       objA.sendmes();

       qDebug()<<"afteremitsignal ";

       returna.exec();

}

如果我們把objB放到另外的執行緒,connect的連線型別應該是Queued Connection 。

 

int main(int argc, char *argv[])

{

       QApplication a(argc, argv);

       QThread *thread = new QThread;

       thread->start();

       Receiver objA,objB;

       objB.moveToThread(thread);

QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) ,Qt::BlockingQueuedConnection);

       qDebug()<<"beforeemitsignal ";

       objA.sendmes();

       qDebug()<<"afteremitsignal ";

       returna.exec();

}

 

顯示的指定連線型別為Qt::BlockingQueuedConnection,則輸出為:

 

http://blog.csdn.net/hai200501019/article/details/9748173