1. 程式人生 > >QT:moveToThread與訊號與槽機制的第五個引數Qt::ConnectionType

QT:moveToThread與訊號與槽機制的第五個引數Qt::ConnectionType

原來對QThread的理解,就是重寫run(),曾經還一度搞不明白,到底它的槽屬於主執行緒還是子執行緒。
後來學了MFC,一度覺得MFC的機制比較人性化,起碼有工作執行緒和介面執行緒的用法,而不像QThread只有run是真正活在子執行緒裡面的。
而直到今天再次研究QThread,發現QThread有很好的功能void QObject::moveToThread(QThread*);
先上程式碼:

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QDebug>
#include <QThread>

class QPushButton;

namespace Ui {
class Widget;
}

class myObject : public QObject
{
    Q_OBJECT
public:
    myObject() {}
    ~myObject() {}

public slots:
    void first() 
    {
        qDebug()<< QThread::currentThreadId();
    }
    void second() 
    {
        qDebug()<< QThread::currentThreadId();
    }
    void third() 
    {
        qDebug()<< QThread::currentThreadId();
    }
};

class Widget : public QWidget
{
    Q_OBJECT
    
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    
private:
    Ui::Widget *ui;
    myObject *my;
    QPushButton *firstButton,*secondButton,*thirdButton,*selfButton;

public slots:
    void onFirstPushed();
    void onSelfPushed();
};

#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QVBoxLayout>
#include <QPushButton>


Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    my = new myObject;
    firstButton = new QPushButton(tr("firstBtn"), 0);
    connect(firstButton, SIGNAL(clicked()), this, SLOT(onFirstPushed()));
    secondButton = new QPushButton(tr("secondBtn"), 0);
    connect(secondButton, SIGNAL(clicked()), my, SLOT(second()), Qt::DirectConnection);
    thirdButton = new QPushButton(tr("thirdBtn"), 0);
    connect(thirdButton, SIGNAL(clicked()), my, SLOT(third()), Qt::QueuedConnection);
    selfButton = new QPushButton(tr("selfBtn"), 0);
    connect(selfButton, SIGNAL(clicked()), this, SLOT(onSelfPushed()));

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(firstButton);
    layout->addWidget(secondButton);
    layout->addWidget(thirdButton);
    layout->addWidget(selfButton);

    this->setLayout(layout);

    QThread *thread = new QThread;
    my->moveToThread(thread);
    connect(thread, SIGNAL(started()), my, SLOT(first()));
    thread->start();
}

Widget::~Widget()
{
    delete ui;
}

void Widget::onFirstPushed() 
{
    my->first();
}

void Widget::onSelfPushed() 
{
    qDebug() << QThread::currentThreadId();
}


    先說毫無疑問的兩個功能:一是程式開始,執行緒啟動,而my線上程中,my線上程中列印執行緒號;二是selfBtn,connect的槽函式完完全全屬於主執行緒的函式,列印主執行緒執行緒號。
    接下來是三個重點的按鈕,三個按鈕的連線方式是不一樣的。
    firstBtn連線的是主執行緒的槽函式,再在槽函式中執行first(),這樣first()是在主執行緒中呼叫的,打印出來的是主執行緒的ID;
    secondBtn直接連線myObject中的槽函式,使用的是Qt:DirectConnection直接連線模式,此模式下訊號與槽是同步的,訊號發出後,直接在訊號傳送者執行緒中呼叫槽函式,由於訊號是主執行緒發出的,因此列印的也是主執行緒的ID;

    thirdBtn也是直接連線myObject中的槽函式,但使用的是QT::QueuedConnection佇列連線模式,此模式下訊號與槽是非同步的,訊號發出後,會進入佇列中,直到控制權到了接收物件(my)屬於的執行緒(thread)的事件迴圈時,槽函式才被呼叫,因此此時列印的是子執行緒thread的執行緒ID。

    這裡重點介紹下connect的第五個引數Qt::ConnectionType。此引數可以有三種選擇Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection,分別是自動連線,直接連線和佇列連線。正如上面所說,直接連線是同步的,槽函式將和訊號同一執行緒,而佇列連線是非同步的,槽函式會屬於接收物件所屬執行緒。而自動連線是預設選項,將自動選擇直接連線和佇列連線之一。而什麼時候選擇什麼連線呢,傳送者和接收者處於相同執行緒,選擇直接連線;傳送者和接收者處於不同執行緒,使用佇列連線。(看過一篇很有趣的驗證文,有興趣可以參考http://www.cppblog.com/andreitang/archive/2011/08/26/154392.html

    對於上面程式碼而言QPushButton的三個connect都屬於傳送者和接收者不在同一個執行緒,不使用第五個引數的情況下,是預設使用佇列連線的,而thread的connect屬於傳送者和接收者在同一執行緒,都是thread的執行緒,預設使用直接連線。
ps:需要注意的是,雖然在connect時,my由主執行緒生成,還沒moveToThread,還在主執行緒,但在執行時,訊號發出的時候,my已經在子執行緒了,因此自動連線下還是會選擇佇列連線。自動連線的選擇並不是在於物件生成的執行緒,而是在於物件所處的執行緒決定的。

    好了總結完畢,準備將執行緒加入到專案中。。。
End