Qt线程之QRunnable的使用详解
概述
說到線程通常會想到QThread,但其實Qt中創(chuàng)建線程的方式有多種,這里主要介紹其中一種QRunnable,QRunnable和QThread用法有些不同,并且使用場景也有區(qū)別。接下來就來看看QRunnable的用法、使用場景以及注意事項。
用法
要使用QRunnable創(chuàng)建線程,步驟如下:
- 繼承QRunnable。和QThread使用一樣, 首先需要將你的線程類繼承于QRunnable。
- 重寫run函數(shù)。還是和QThread一樣,需要重寫run函數(shù),run是一個純虛函數(shù),必須重寫。
- 使用QThreadPool啟動線程
和QThread的區(qū)別
- 與外界通信方式不同。由于QThread是繼承于QObject的,但QRunnable不是,所以在QThread線程中,可以直接將線程中執(zhí)行的結(jié)果通過信號的方式發(fā)到主程序,而QRunnable線程不能用信號槽,只能通過別的方式,等下會介紹。
- 啟動線程方式不同。QThread線程可以直接調(diào)用start()函數(shù)啟動,而QRunnable線程需要借助QThreadPool進行啟動。
- 資源管理不同。QThread線程對象需要手動去管理刪除和釋放,而QRunnable則會在QThreadPool調(diào)用完成后自動釋放。
示例
首先我們新建一個類,繼承于QRunnable
#include <QRunnable> #include <QWidget>class CusRunnable : public QRunnable { public:explicit CusRunnable();~CusRunnable();void run(); };#endif // CUSRUNNABLE_H源文件
#include "cusrunnable.h" #include <QDebug> #include <QThread>CusRunnable::CusRunnable() { }CusRunnable::~CusRunnable() {qDebug() << __FUNCTION__; }void CusRunnable::run() {qDebug() << __FUNCTION__ << QThread::currentThreadId();QThread::msleep(1000); }然后在主界面中調(diào)用該線程。
#include <QWidget> #include "cusrunnable.h"class Widget : public QWidget {Q_OBJECT public:Widget(QWidget *parent = 0);~Widget(); private:CusRunnable * m_pRunnable = nullptr; };#endif // WIDGET_H #include "widget.h" #include <QThreadPool> #include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent) {m_pRunnable = new CusRunnable(this);qDebug() << __FUNCTION__ << QThread::currentThreadId();QThreadPool::globalInstance()->start(m_pRunnable); }Widget::~Widget() {qDebug() << __FUNCTION__ ; }輸出結(jié)果:
Widget::Widget 0x199c CusRunnable::run 0x1bcc CusRunnable::~CusRunnable我們可以看到這里打印的線程ID是不同的,說明是在不同線程中執(zhí)行,而線程執(zhí)行完后就自動進入到析構(gòu)函數(shù)中, 不需要手動釋放。
啟動線程方式
上面我們說到要啟動QRunnable線程,需要QThreadPool配合使用,而調(diào)用方式有兩種:全局線程池和非全局線程池。
全局線程池
上面示例中啟動方式就是用的全局線程池來啟動QRunnable,使用很簡單:
QThreadPool::globalInstance()->start(m_pRunnable);非全局線程池
除此之外,還可以使用非全局線程池的方式來實現(xiàn),該方式可以控制線程最大數(shù)量, 以及其他設(shè)置,比較靈活,具體參照幫助文檔。
QThreadPool threadpool;threadpool.setMaxThreadCount(1);threadpool.start(m_pRunnable);與外界通信
前面我們提到,因為QRunnable沒有繼承于QObject,所以沒法使用信號槽與外界通信,那么,如果要在QRunnable線程中和外界通信怎么辦呢,通常有兩種做法:
- 使用多繼承。讓我們的自定義線程類同時繼承于QRunnable和QObject,這樣就可以使用信號和槽,但是多線程使用比較麻煩,特別是繼承于自定義的類時,容易出現(xiàn)接口混亂,所以在項目中盡量少用多繼承。
- 使用QMetaObject::invokeMethod。
QMetaObject::invokeMethod
函數(shù)定義如下:
[static] bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, 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())該函數(shù)就是嘗試調(diào)用obj的member函數(shù),可以是信號、槽或者Q_INVOKABLE聲明的函數(shù)(能夠被Qt元對象系統(tǒng)喚起),如果調(diào)用成功,返回true,失敗返回false,具體使用方法就不在這里介紹。
QMetaObject::invokeMethod可以是異步調(diào)用,也可以是同步調(diào)用。這取決與它的連接方式Qt::ConnectionType type。如果type為Qt::DirectConnection,則為同步調(diào)用,若為Qt::QueuedConnection,則為異步調(diào)用。
來看一下,在上面的示例中,我們?nèi)绾瓮ㄟ^QMetaObject::invokeMethod讓QRunnable線程與外部主線程通信。
假如我們在主界面中定一個函數(shù),用于更新界面內(nèi)容:
Q_INVOKABLE void setText(QString msg);然后線程類需要修改一下:
class CusRunnable : public QRunnable { public:explicit CusRunnable(QObject *obj);~CusRunnable();void run();private:QObject * m_pObj = nullptr; }; CusRunnable::CusRunnable(QObject * obj):m_pObj(obj) { }CusRunnable::~CusRunnable() {qDebug() << __FUNCTION__; }void CusRunnable::run() {qDebug() << __FUNCTION__ << QThread::currentThreadId();QMetaObject::invokeMethod(m_pObj,"setText",Q_ARG(QString,"this is AA!"));QThread::msleep(1000); }注意,這里的調(diào)用方式:
QMetaObject::invokeMethod(m_pObj,"setText",Q_ARG(QString,"this is AA!"));其中"setText"就是要調(diào)用的函數(shù),傳參方式Q_ARG(QString,"this is AA!"),表示傳入一個QString類型,值為"this is AA!"
而在創(chuàng)建線程對象時,需要將主界面對象傳入線程類
m_pRunnable = new CusRunnable(this);這樣一來就可以在線程中和外界通信了。
遇到的問題
使用QThreadPool調(diào)用線程后,線程執(zhí)行完會自動釋放資源,但是我使用的時候遇到一個問題,未找到原因。就是在該線程對象都已經(jīng)釋放過后(已經(jīng)進入到析構(gòu)函數(shù)了),該對象還可以繼續(xù)使用,還可以使用該對象調(diào)用線程類中的函數(shù),也就是說,理論上該對象指針已經(jīng)釋放了,這時候變成了野指針,并且線程對象值不為空。
做了一個試驗,在執(zhí)行完線程后,再次使用線程對象指針調(diào)用線程類中的函數(shù),結(jié)果還是可以用。
關(guān)鍵代碼如下:
這里每次點擊按鈕,就會調(diào)用一次線程類中的函數(shù)。輸出如下:
Widget::Widget 0x3794 CusRunnable::run 0x39e0 Widget::setText "this is AA!" CusRunnable::~CusRunnable Widget::{ctor}::<lambda_e0d3aa46abd43a56277208964007553d>::operator () 0x1045348 Widget::{ctor}::<lambda_e0d3aa46abd43a56277208964007553d>::operator () 0x1045348從上面可以看到,已經(jīng)進入到了析構(gòu)函數(shù),但是依然可以使用該對象調(diào)用其中的函數(shù),并且對象也不為空。
再次試驗,在主界面析構(gòu)函數(shù)中判斷:
Widget::~Widget() {qDebug()<< __FUNCTION__ ;if(m_pRunnable){delete m_pRunnable;m_pRunnable = nullptr;} }關(guān)閉程序的時候,會出現(xiàn)異常退出,也就是說,該指針確實釋放了,不能再次delete,變成了一個野指針。
所以最終結(jié)論,QThreadPool在調(diào)用線程執(zhí)行完后確實會釋放資源,但是并不會將對象置空,這時候?qū)ο笞兂闪艘粋€野指針,如果還是用這個指針的話可能會出現(xiàn)隱患錯誤,所以最好不好這樣調(diào)用。而在程序最終退出時,可以將該線程對象置空,但是不能再次delete,否則將會異常。
總結(jié)
以上是生活随笔為你收集整理的Qt线程之QRunnable的使用详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NSIS 打包文件添加防火墙白名单
- 下一篇: Qt中使用线程的几种方式及区别