Qt5中對於多線程和事件的一些理解

讀了《Qt學習之路2》多線程相關章節,說實話讀了5遍之後才理解多線程和QObject之間的關係。個人感覺理解Qt5多線程最重要的是理解書中所謂的線程依賴的關係。線程依賴是相對於QObject對象而言的,也就是說我們要辨別清楚某個QObject的所依賴的線程是哪一個。

Qt5因爲每個QThread的子類的run函數都默認調用了exec(),每個線程都有各自單獨的事件循環機制。所以Qt5中推薦我們實現多線程不再是像之前的自定義一個類繼承QThread,然後在QThread中的run函數實現自己的業務代碼。而是利用線程自己的事件循環機制來調用依賴這個線程的QObject的slot槽函數實現多線程的業務代碼。那麼如何讓一個QObject依賴於一個線程呢?有兩種方法:

1.在線程的run()函數中創建的QObject必定依賴這個線程。

2.使用QObject::moveToThread(pThread)函數將QObject依賴於pThread所代表的線程。

當一個QObject依賴於一個線程的時候,那麼QObject的slot槽函數也將會在所依賴線程的run函數中去執行,也就達到了我們在線程中執行一個耗時操作而不影響Qt主GUI線程了,即使slot槽函數中是一些耗時操作,它阻塞的也只是所依賴線程的事件循環。總的來說也就是QObject在哪個線程中創建的,QObject依賴哪個線程(除非使用moveToThread修改了線程依賴性),那麼QObject的槽函數就會在哪個線程的run()函數中執行。下面列舉一個簡單的例子

//===============繼承QThread的工作線程類=======================
#ifndef CKVTHREAD_H
#define CKVTHREAD_H

#include <QObject>
#include <QThread>

class CKvThread : public QThread
{
public:
    CKvThread(){
        qDebug()<<"構造函數"<<endl;
    }
    ~CKvThread(){
        qDebug()<<"析構函數"<<endl;
    }
};
#endif // CKVTHREAD_H
//=============================================================


//===============實現具體業務代碼QObject類=======================
class CKvOptimize : public QObject
{
    Q_OBJECT
public:
    CKvOptimize();
private slots:
    //耗時的槽函數
    void slot_Solve(void){
        int i = 0;
        while(i < 30){//slot耗時30s
            Sleep(1000);//睡眠1s
            i++;
        }
    }
};
//=============================================================


//Qt主GUI線程
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    pTestThread = new CKvThread();
    pTestOpti = new CKvOptimize();

    //使得pTestOpti這個QObject依賴線程pTestThread所代表的線程
    pTestOpti->moveToThread(this->pTestThread);

    //使用信號槽機制鏈接信號和槽函數
    connect(this->ui->bt_testOpti/*主界面上的一個按鈕*/ , SIGNAL(clicked(bool)) , this->pTestOpti , SLOT(slot_Solve(void)));

    //開始線程事件循環
    pTestThread->start();
}

在這個例子中pTestOpti這個QObject是在主線程中創建的,所以應該依賴於主線程,但是由於使用了moveToThread將其依賴性線程設置爲了pTestThread所代表的線程,所以最終pTestOpti依賴pTestThread所代表的線程。而pTestOpti得耗時槽函數slot_solve()也將在pTestThread的run()中執行而不會阻塞主線程。

另外一個重要的點是需要正確理解Qt的四種信號槽鏈接模式:

1.Qt::DirectConnection直接鏈接:槽函數將在發送信號的線程直接執行調用槽函數。

2.Qt::QueuedConnection隊列鏈接:像信號接受對象所依賴的線程發送一個事件,該線程注意處理自己的事件循環隊列中的事件在合適的時候執行調用槽函數。

3.Qt::BlockingQueuedConnection阻塞隊列鏈接:和第二種鏈接方式一樣,但是不同的是發送信號的線程會阻塞直到槽函數被調用執行完成返回。

4.Qt::AutoConnection自動鏈接(默認鏈接方式):如果發送信號的線程和信號接受對象所依賴的線程相同則使用Qt::DirectConnection否則使用t::QueuedConnection。

注意上面的解釋中使用的字眼是“發送信號的線程”而不是“發送信號對象所依賴的線程”。而且可以看出鏈接方式中除了自動鏈接模式以外,槽函數在哪個線程中執行和發送者沒有關係,其實只需要關心接受對象所依賴的線程是哪一個即可。

附錄注意事項:

1.QObject和其子對象必須都依賴於同一個線程,所以不能對QObject的子對象調用moveToThread函數僅僅只改變子對象的線程依賴性。

2.QThread對象並不是線程本身,它只是用於管理它所代表的線程。所以正確的用法應該是在它所依賴的線程中被使用(通常就是創建QThread對象的線程)而不應該在它所代表的線程中使用。

3.事件循環是在Qt組件層次傳播的,而不是依靠類的繼承機制來傳播。正因爲事件是在組件層次傳播的,所以事件會傳遞給各個窗口。

4.在使用組件的時候我們只需要關心信號槽就可以了,當我們需要自定義組件的時候才需要關心事件,因爲實際上信號需要在我們自定義組件中的事件處理函數中去發出。

5.事件其實是操作系統的特定程序進程來捕獲的,然後通過進程間通信機制將事件信息傳遞到Qt應用程序的。Qt的主事件循環處理函數QApplication.exec()其實就是不斷的處理操作系統發送到Qt應用程序事件隊列中的事件。

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