QThread 與 QObject的關係

QThread 與 QObject的關係

Threads and QObjects

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

       return a.exec();

}

輸出結果: MyThread所在的線程就是主線程,run()函數是新開的線程。

QObject Reentrancy

QObject 是 可重入的 ,它的大多數非 GUI 子類,例如 QTimer ,  QTcpSocket QUdpSocket  and  QProcess 都是可重入的,使得這些類可以同時用於多線程。需要注意的是,這些類設計爲從一個單一的線程創建和使用的,在一個線程創建對象,而從另外一個線程調用對象的函數並不能保證行得通。有三個限制需要注意:

1.     QObject 的子對象必須在創建其parent 的線程中創建。這意味着,你不能把QThread 對象作爲parent傳遞給創建在線程中的對象,因爲 QThread  對象本身在另外一個線程中創建。

2.     事件驅動對象只能用於單線程。尤其是在定時器機制和網絡模塊。例如,你不能在不是對象所處的線程 start 一個計時器或者鏈接一個 secket 。簡單的說就是,你不能在線程 A 創建了一個計時器 timer ,然後在線程 B 從啓動 timer 

我們可以驗證一下:

class MyThread : public QThread

{

       Q_OBJECT

public :

       MyThread(QObject *parent = NULL);

       ~MyThread();

public slots :

       void timeOutSlot();

protected :

       void run();

       QTimer *m_pTimer;

};

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

{

       m_pTimer = new QTimer( 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!" );

}

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

{

       QApplication a(argc, argv);

       MyThread thread;

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

       thread.start();

       return a.exec();

}

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

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

void QEventDispatcherWin32::registerTimer( int timerId, int interval, 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 類,例如 (QTimer  QTcpSocket , and  QProcess ). 。也可以把任意的線程的信號連接到特定線程的槽。

QObject 實例存在於創建實例的線程中,發送給實例事件也是有線程的事件循環實現的。可以用   QObject::thread (). 獲取對象存活於哪個線程。

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

{

       m_pTimer = new QTimer( 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 = new QTimer();

       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 = new QTimer();

       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: public QObject

{

       Q_OBJECT

public :

       void sendmes()

       {

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

       }

       Receiver()

       {

       }

protected slots :

       void messageSlot(QString mes)

       {

              qDebug()<<mes;

       }

signals :

       void emitSignal(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 " ;

       return a.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 " ;

       return a.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 " ;

       return a.exec();

}

 

顯示的指定連接類型爲 Qt::BlockingQueuedConnection,則輸出爲:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章