執行緒介紹(QThread)

一個執行緒(Thread)是程序(Process)中的一個執行流程,一個程序可以同時包括多個執行緒,使得一個程式可以像是同時處理多個事務,例如可以一方面接受網路上的資料,另一方面同時接受使用者輸入的訊息。

Qt有些類別,本身操作就設計為非同步,像Signal與Slot的使用,不用另外增加多執行緒,也可以實現非阻斷的操作,但有些時候,我們必須實作多執行緒,才不會組塞主執行緒流程。


以下範例有一個用來顯示時間的面板,還有一個用於開啟耗時計算的按鈕,當使用者點擊此按鈕,程式開始一個冗長的for迴圈,實務上可能是其他需要耗時的功能。一開始程式正常更新時間,但是當我們點擊按鈕之後,面板停止更新時間,且程序界面組塞,直到計算結束才恢復正常。


widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QTimer>
#include <QTime>
#include <QLCDNumber>
#include <QVBoxLayout>
#include <QPushButton>
#include <QMessageBox>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
private:
    QTimer *myTimer;
    QLCDNumber *myLCDNumber;
    QVBoxLayout *layout;
    QPushButton *startBtn;
private slots:
    void showTime();
    void startCalc();
};

#endif 

widget.cpp

#include "widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent)
{
    setWindowTitle(tr("按鍵組"));
    myLCDNumber = new QLCDNumber;
    myLCDNumber->setDigitCount(8);    //設定位數
    layout = new QVBoxLayout(this);
    startBtn = new QPushButton;
    startBtn->setText(tr("開啟耗時計算"));
    layout->addWidget(myLCDNumber);
    layout->addWidget(startBtn);

    myTimer = new QTimer(this);
    myTimer->start(1000);             //以1000毫秒為周期起動定時器
    showTime();

    connect(myTimer,SIGNAL(timeout()),this,SLOT(showTime()));
    connect(startBtn,SIGNAL(clicked()),this,SLOT(startCalc()));
}

void Widget::showTime(){
    QTime time = QTime::currentTime();
    QString text=time.toString("hh:mm:ss"); //設定顯示時間格式
    myLCDNumber->display(text);
}

void Widget::startCalc(){
    for(int i = 0; i < 3000000000; i++){
        int a = 0;
    }
    QMessageBox::information(NULL,tr("消息框"),("計算完畢"));
}

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

因為Qt中所有界面都是在UI執行緒中(也被稱為主執行緒,就是執行QApplication::exec()的線程),在這個線程中執行耗時的操作,UI就會阻塞,而讓介面停止響應,為了避免這一問題,我們改寫程式,使用QThread開啟一個新的執行緒,由這新的執行緒進行耗時的工作,而主執行緒可繼續進行使用者介面的響應。

我們新建一個WorkerThread類別,WorkerThread繼承自QThread類別,重載run()函式,run()函式內容為新執行緒要進行的工作,也就是剛剛那個耗時計算,當使用者點擊按鈕時,我們使用QThread::start()函式啟動一個執行緒,這時Qt會執行重載的那個run()函式,我們不該自行呼叫run()函式,當函式內容執行完畢時,系統會幫我們清除線程實例。這時我們可發現,現在開啟耗時運算時,介面已經不會被阻塞了。


workthread.h

#ifndef WORKTHREAD
#define WORKTHREAD

#include <QThread>

class WorkerThread : public QThread
{ 
    Q_OBJECT
public:
    WorkerThread(QObject *parent = 0) : QThread(parent){ }
protected:
    void run(){
        for(int i = 0; i < 3000000000; i++){
            int a = 0;
        }
        emit done();
    }
signals:
    void done();
};

#endif 

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QTimer>
#include <QTime>
#include <QLCDNumber>
#include <QVBoxLayout>
#include <QPushButton>
#include <QMessageBox>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
private:
    QTimer *myTimer;
    QLCDNumber *myLCDNumber;
    QVBoxLayout *layout;
    QPushButton *startBtn;
private slots:
    void showTime();
    void finishCalc();
};

#endif 

widget.cpp

#include "widget.h"
#include "workthread.h"
Widget::Widget(QWidget *parent) :
    QWidget(parent)
{
    setWindowTitle(tr("按鍵組"));
    myLCDNumber = new QLCDNumber;
    myLCDNumber->setDigitCount(8);     //設定位數
    layout = new QVBoxLayout(this);
    startBtn = new QPushButton;
    startBtn->setText(tr("開啟耗時計算"));
    layout->addWidget(myLCDNumber);
    layout->addWidget(startBtn);

    myTimer = new QTimer(this);
    myTimer->start(1000);              //以1000毫秒為周期起動定時器
    showTime();

    WorkerThread *thread = new WorkerThread(this);
    connect(myTimer,SIGNAL(timeout()),this,SLOT(showTime()));
    connect(startBtn,SIGNAL(clicked()),thread,SLOT(start()));
    connect(thread,SIGNAL(done()),this,SLOT(finishCalc()));
}

void Widget::showTime(){
    QTime time = QTime::currentTime();
    QString text=time.toString("hh:mm:ss"); //設定顯示時間格式
    myLCDNumber->display(text);
}

void Widget::finishCalc(){
    QMessageBox::information(NULL,tr("消息框"),("計算完畢"));
}

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}