【QT】繼承QRunnable+QThreadPool實現多線程


往期鏈接:

繼承QRunnable+QThreadPool實現多線程的方法個人感覺使用的相對較少,在這里只是簡單介紹下使用的方法。我們可以根據使用的場景來選擇方法。

此方法和QThread的區別:

  • 與外界通信方式不同。由於QThread是繼承於QObject的,但QRunnable不是,所以在QThread線程中,可以直接將線程中執行的結果通過信號的方式發到主程序,而QRunnable線程不能用信號槽,只能通過別的方式,等下會介紹;
  • 啟動線程方式不同。QThread線程可以直接調用start()函數啟動,而QRunnable線程需要借助QThreadPool進行啟動;
  • 資源管理不同。QThread線程對象需要手動去管理刪除和釋放,而QRunnable則會在QThreadPool調用完成后自動釋放。

接下來就來看看QRunnable的用法、使用場景以及注意事項;

一、步驟

要使用QRunnable創建線程,步驟如下:

  • 繼承QRunnable。和QThread使用一樣, 首先需要將你的線程類繼承於QRunnable;
  • 重寫run函數。還是和QThread一樣,需要重寫run函數;
  • 使用QThreadPool啟動線程。

二、實例

繼承於QRunnable的類:

#ifndef INHERITQRUNNABLE_H
#define INHERITQRUNNABLE_H

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

class CusRunnable : public QRunnable
{
public:
    explicit CusRunnable(){
    }

    ~CusRunnable(){
        qDebug() << __FUNCTION__;
    }

    void run(){
        qDebug() << __FUNCTION__ << QThread::currentThreadId();
        QThread::msleep(1000);
    }
};

#endif // INHERITQRUNNABLE_H

主界面類:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "ui_mainwindow.h"
#include "InheritQRunnable.h"
#include <QThreadPool>
#include <QDebug>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0) :
        QMainWindow(parent),
        ui(new Ui::MainWindow){
        ui->setupUi(this);

        m_pRunnable = new CusRunnable();
        qDebug() << __FUNCTION__  << QThread::currentThreadId();
        QThreadPool::globalInstance()->start(m_pRunnable);
    }

    ~MainWindow(){
        qDebug() << __FUNCTION__ ;
        delete ui;
    }

private:
    Ui::MainWindow *ui;
    CusRunnable * m_pRunnable = nullptr;
};

#endif // MAINWINDOW_H

直接運行以上實例,結果輸出如下:

MainWindow 0x377c
run 0x66ac
~CusRunnable

我們可以看到這里打印的線程ID是不同的,說明是在不同線程中執行,而線程執行完后就自動進入到析構函數中, 不需要手動釋放。

三、啟動線程的方式

上面我們說到要啟動QRunnable線程,需要QThreadPool配合使用,而調用方式有兩種:全局線程池和非全局線程池。

(1)使用全局線程池啟動

QThreadPool::globalInstance()->start(m_pRunnable);

(2)使用非全局線程池啟動

該方式可以控制線程最大數量, 以及其他設置,比較靈活,具體參照幫助文檔。

QThreadPool	  threadpool;
threadpool.setMaxThreadCount(1);
threadpool.start(m_pRunnable);

四、如何與外界通信

前面我們提到,因為QRunnable沒有繼承於QObject,所以沒法使用信號槽與外界通信,那么,如果要在QRunnable線程中和外界通信怎么辦呢,通常有兩種做法:

  • 使用多繼承。讓我們的自定義線程類同時繼承於QRunnable和QObject,這樣就可以使用信號和槽,但是多線程使用比較麻煩,特別是繼承於自定義的類時,容易出現接口混亂,所以在項目中盡量少用多繼承。
  • 使用QMetaObject::invokeMethod。

接下來只介紹使用QMetaObject::invokeMethod來通信:

QMetaObject::invokeMethod 函數定義如下:

static bool QMetaObject::invokeMethod(
                         QObject *obj, const char *member,
                         Qt::ConnectionType,
                         QGenericReturnArgument ret,
                         QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                         QGenericArgument val1 = QGenericArgument(),
                         QGenericArgument val2 = QGenericArgument(),
                         QGenericArgument val3 = QGenericArgument(),
                         QGenericArgument val4 = QGenericArgument(),
                         QGenericArgument val5 = QGenericArgument(),
                         QGenericArgument val6 = QGenericArgument(),
                         QGenericArgument val7 = QGenericArgument(),
                         QGenericArgument val8 = QGenericArgument(),
                         QGenericArgument val9 = QGenericArgument());

該函數就是嘗試調用obj的member函數,可以是信號、槽或者Q_INVOKABLE聲明的函數(能夠被Qt元對象系統喚起),只需要將函數的名稱傳遞給此函數,調用成功返回true,失敗返回false。member函數調用的返回值放在ret中,如果調用是異步的,則不能計算返回值。你可以將最多10個參數(val0、val1、val2、val3、val4、val5、val6、val7、val8和val9)傳遞給member函數,必須使用Q_ARG()和Q_RETURN_ARG()宏封裝參數,Q_ARG()接受類型名 + 該類型的常量引用;Q_RETURN_ARG()接受一個類型名 + 一個非常量引用。

QMetaObject::invokeMethod可以是異步調用,也可以是同步調用。這取決與它的連接方式Qt::ConnectionType type:

  • 如果類型是Qt::DirectConnection,則會立即調用該成員,同步調用。
  • 如果類型是Qt::QueuedConnection,當應用程序進入主事件循環時,將發送一個QEvent並調用該成員,異步調用。
  • 如果類型是Qt::BlockingQueuedConnection,該方法將以與Qt::QueuedConnection相同的方式調用,不同的地方:當前線程將阻塞,直到事件被傳遞。使用此連接類型在同一線程中的對象之間通信將導致死鎖。
  • 如果類型是Qt::AutoConnection,如果obj與調用者在同一線程,成員被同步調用;否則,它將異步調用該成員。

我們在主界面中定一個函數,用於更新界面內容:

Q_INVOKABLE void setText(QString msg){
    ui->label->setText(msg);
}

繼承於QRunnable的線程類,修改完成如下:

#ifndef INHERITQRUNNABLE_H
#define INHERITQRUNNABLE_H

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

class CusRunnable : public QRunnable
{
public:
    //修改構造函數
    explicit CusRunnable(QObject *obj):m_pObj(obj){
    }

    ~CusRunnable(){
        qDebug() << __FUNCTION__;
    }

    void run(){
        qDebug() << __FUNCTION__ << QThread::currentThreadId();
        QMetaObject::invokeMethod(m_pObj,"setText",Q_ARG(QString,"hello world!")); //此處與外部通信
        QThread::msleep(1000);
    }

private:
    QObject * m_pObj = nullptr; //定義指針
};

#endif // INHERITQRUNNABLE_H

創建線程對象時,需要將主界面對象傳入線程類,如下:

m_pRunnable = new CusRunnable(this);

到這里也就實現了線程與外部通信了,運行效果如下:

五、小結

  • 使用該方法實現的多線程,線程中的資源無需用戶手動釋放,線程執行完后會自動回收資源;
  • 和繼承QThread的方法一樣需要繼承類,並且重新實現run函數;
  • 需要結合QThreadPool線程池來使用;
  • 與外界通信可以使用如果使用信號槽機制會比較麻煩,可以使用QMetaObject::invokeMethod的方式與外界通信。

本文章實例的源碼地址:https://gitee.com/CogenCG/QThreadExample.git


免責聲明!

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



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