QT多線程的使用


Qt中提供了對於線程的支持,它提供了一些獨立於平台的線程類,要進行多線程方法,可以有兩種方式。


1. 第一種方式

qt提供QThread類,在QThread類中有一個virtual函數QThread::run()。

要創建一個新的線程,我們只需定義一個MyThread類,讓其繼承QThread,然后重新實現QThread::run()。

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    ~MyThread();

protected:
    //QThread的虛函數
    //線程處理函數
    //不能直接調用,通過start()間接調用
    void run();

signals:
    void isDone();  //處理完成信號

signals:

public slots:
};

#endif // MYTHREAD_H

};

然后可以在run中寫入要進行的操作,比如可以讓其等待5秒。若不是多線程,在運行時我們單擊窗口,窗口會出現無響應的狀態。

那如何通知線程結束?這就可以用qt的信號和槽機制了,我們可以在操作完成時發出一個完成信號,完成信號我們在聲明文件里已經定義了。

void MyThread::run()
{
    //很復雜的數據處理
    //需要耗時5秒
    sleep(5);

    emit isDone();  //發送完成信號
}

這樣,我們就把線程的操作寫完了。

現在,我們先來布一個簡單的ui,只用到了一個LcdNumber和一個PushButton。

在當前widget的頭文件中定義一些需要用到的操作。並加入我們定義的線程文件。

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>
#include <QTimer>
#include "mythread.h"  //線程頭文件


namespace Ui {
class MyWidget;
}

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    explicit MyWidget(QWidget *parent = 0);
    ~MyWidget();

    void dealTimeout();  //定時器槽函數
    void dealDone();   //線程槽函數
    void stopThread();  //停止線程

private slots:
    void on_pushButton_clicked();

private:
    Ui::MyWidget *ui;

    QTimer *myTimer;  //聲明變量
    MyThread *thread;  //線程對象
};

#endif // MYWIDGET_H

我們用一個timer定時器來讓Lcd控件按指定時間更新數字,當我們點擊開始按鈕時,定時器啟動,自動觸發timerout信號。捕獲timerout信號,在dealTimerout()函數中寫入需要進行的操作,當捕獲到timerout時,自動使用dealTimerout槽函數。

dealTimerout()我們可以這樣寫:


void MyWidget::dealTimeout()
{
    static int i = 0;
    i++;
    //設置lcd的值
    ui->lcdNumber->display(i);
}

在widget的構造函數中,先創建一個定時器並為線程函數分配空間:

myTimer = new QTimer(this);

thread = new QThread(thread);  //分配空間

然后我們關聯信號和槽:

//只要定時器啟動,自動觸發timeout
connect(myTimer, &QTimer::timeout, this, &MyWidget::dealTimeout);

在開始按鈕的槽函數中,啟動定時器,並開啟線程,需要注意的是,我們不能直接調用run函數,要通過start()間接調用線程函數。

void MyWidget::on_pushButton_clicked()
{
    //若定時器沒有工作
    if(myTimer->isActive() == false)
    {
        myTimer->start(100);
    }

    //啟動線程,處理數據
    thread->start();
}

線程結束時我們接收到isDone信號,我們在其中關閉定時器:

void MyWidget::dealDone()
{
    qDebug() << "it is over";  //打印線程結束信息
    myTimer->stop();  //關閉定時器
}

我們選擇在退出窗口時關閉線程,退出窗口時會觸發destroyed信號,線程關閉的槽函數實現如下:

void MyWidget::stopThread()
{
    //停止線程
    thread->quit();

    //等待線程處理完手頭工作
    thread->wait();
}

最后,在widget主窗口的構造函數中加入線程的關聯信號和槽:

connect(thread, &MyThread::isDone, this, &MyWidget::dealDone);

//當按窗口右上角x時,觸發destroyed信號
connect(this, &MyWidget::destroyed, this, &MyWidget::stopThread);

現在,當我們啟動程序時,窗口如下:

當我們點擊Start按鈕時,觸發定時器,開啟線程,每100ms更新一次Lcd中的數值:

5秒后,線程停止,發出isDone信號,執行dealDone槽函數,顯示it is over並關閉計時器:

再次點擊,再次啟動定時器,繼續累加數字並設置到Lcd中,點擊x,程序退出,停止線程。


2. 第二種方式

新建一個工程,ui如圖:

新建一個類,繼承自QObject,然后在類中設置一個線程函數。

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>

class MyThread : public QObject
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    ~MyThread();

    //線程處理函數
    void MyTimeout();

    void setFlag(bool flag = true);


signals:
    void mySignal();

public slots:

private:
    bool isStop;
};

#endif // MYTHREAD_H

通過一個bool變量來控制線程結束,通過發出mySignal()信號來調用處理槽函數。

在widget中定義如下:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "mythread.h"
#include <QThread>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

    void dealSignal();
    void dealClose();

signals:
    void startThread();  //啟動子線程的信號

private slots:
    void on_pushButtonStart_clicked();

    void on_pushButtonStop_clicked();

private:
    Ui::Widget *ui;

    MyThread *myT;
    QThread *thread;
};

#endif // WIDGET_H

兩個槽函數dealSignal()和dealClose()分別關聯着mySignal()和destroyed()信號。

信號startThread()用於啟動子線程。

在widget的實現中,


  • 首先創建一個線程對象,需要注意不能指定父對象。
    myT = new MyThread;

  • 創建一個Qthread子線程對象
    QThread *thread = new QThread(this);

  • 把我們的自定義線程類,加入到子線程(若是myT指定了父對象,此處就會出錯。)
    my->moveToThread(thread);

  • 啟動子線程,只是把線程開啟了,並沒有啟動線程處理函數
    thread.start();

  • 線程的啟動,必須通過signal - slot的方式。

各種函數實現如下:

void Widget::dealSignal()
{
    static int i = 0;
    i++;
    ui->lcdNumber->display(i);
}

//Start按鈕
void Widget::on_pushButtonStart_clicked()
{
    if(thread->isRunning() == true)
    {
        return;
    }

    //啟動線程,但是沒有啟動線程處理函數
    thread->start();
    myT->setFlag(false);

    //不能直接調用線程處理函數,直接調用導致線程處理函數和主線程在同一個線程
    //myT->MyTimeout();

    //只能通過 signal - slot方式
    emit startThread();
}

//Stop按鈕
void Widget::on_pushButtonStop_clicked()
{
    if(thread->isRunning() == false)
    {
        return;
    }

    myT->setFlag(true);
    thread->quit();
    thread->wait();
}

void Widget::dealClose()
{
    on_pushButtonStop_clicked();
    delete myT;
}

dealSignal中,使Lcd數字遞增。

當按下Start按鈕,啟動線程,設置線程標志為flase,通過發出startThread()信號來調用真正的線程函數MyThread::MyTimeout。

當按下Stop按鈕時,設置線程標志為true,關閉線程。

由於未給myT指定父對象,所以需要我們手動來釋放內存,當點擊x時,關閉線程,delete釋放。

運行如圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM