往期鏈接:
- 《QThread原始碼淺析》
- 《子類化QThread實作多執行緒》
- 《子類化QObject+moveToThread實作多執行緒》
- 本文章實體的原始碼地址:https://gitee.com/CogenCG/QThreadExample.git
繼承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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/209248.html
標籤:其他
