一、需求來源
對於使用Qt線程,有兩種方式,見本人其他文章:https://www.cnblogs.com/judes/p/6884964.html
個人認為QObject::moveToThread方式是最好的,無需死循環判斷某個條件是否成立,如此非常消耗CPU【用C++11條件變量可解決】
所以翻遍整個網絡也想要找到QML+moveToThread操作線程的方式。
我理想中的工作模式是:
所有工作類【如網絡、串口等】繼承於QObject,然后moveTothread到某個QThread對象,QML里通過信號與槽的方式控制工作類的開關及數據收發。
無奈,只有此博主提及了一下:https://blog.csdn.net/LHRui_1/article/details/83861142,我最開始的做法與此博主開始一致:
在main.cpp里moveTothread,然后注冊這個對象到上下文,再在qml直接訪問對象內部。
這樣是行不通的,會提示錯誤:不能訪問非當前線程對象..【大致意思是這樣】
因為:main.cpp里,工作對象是子線程的【已經moveTothread了】,而engine是主線程的,使用主線程engine注冊子線程工作對象到上下文,然后在QML里調用,肯定會出錯,具體原因也不是很明白,實在沒有相關資料了。
二、解決
還好,放棄baidu搜索,換了bing國際版搜索,看到一篇文章:https://forum.qt.io/topic/62073/qthread-qml
博士非常謙虛,沒有諷刺QThread子類方法【因為官方也認可這種方法,自然有人使用。而本人非常相信這是不夠“效率”的做法,與使用此方法的朋友爭論許久,最后放棄。或許代碼界沒有答案】,
只聲明這是ta自己的opinions。在看了文章之后發現,這就是我想要的效果,並且我相信是最高效正確的做法。
1、工作類work.h
#ifndef WORKER_H #define WORKER_H #include <QObject> #include <QDebug> #include <QThread> class Worker: public QObject { Q_OBJECT public: Worker(QString &data) : mData(data) {} public slots: void process() { qDebug() << "Process's Thread : " << QThread::currentThreadId(); mData += "process\n"; emit processFinished(); } signals: void processFinished(); private: QString &mData; }; class WorkerInterface : public QObject { Q_OBJECT Q_PROPERTY(QString data READ getData NOTIFY dataChanged) public: WorkerInterface() : mWorker(mData) { mWorker.moveToThread(&mThread); connect(this, &WorkerInterface::process, &mWorker, &Worker::process); connect(&mWorker, &Worker::processFinished, [this]{ qDebug() << "ProcessFinished in : " << QThread::currentThreadId(); emit dataChanged(); }); mThread.start(); } QString getData() const { return mData; } ~WorkerInterface() { mThread.exit(); mThread.wait(); } signals: void dataChanged(); void process(); private: QThread mThread; QString mData; Worker mWorker; }; #endif // WORKER_H
2、main.cpp
#include <QApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QQmlComponent> #include "worker.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); qmlRegisterType<WorkerInterface>("Workers", 1, 0, "Worker"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); qDebug() << "Main thread : " << QThread::currentThreadId(); return app.exec(); }
3、main.qml
import QtQuick 2.5 import QtQuick.Controls 1.4 import Workers 1.0 ApplicationWindow { visible: true width: 640 height: 480 title: qsTr("Hello World") SystemPalette { id: pal colorGroup: SystemPalette.Active } Worker { id: worker; } Button { id: button text: qsTr("Process") anchors.centerIn: parent onClicked: worker.process(); } Text { text: worker.data anchors.horizontalCenter: parent.horizontalCenter anchors.top: button.bottom anchors.topMargin: 20 } }
三、剖析
1、定義工作類Worker繼承於QObject
2、定義連接QML和C++的中間接口類WorkerInterface繼承於QObject
3、在中間類WorkerInterface中聲明工作類的對象mWorker
4、在中間類WorkerInterface構造函數中連接信號與槽,mWorker::moveToThread,變量初始化等
5、注冊中間類WorkerInterface
6、在QML中實例化中間類,並直接調用其信號控制工作類開啟其槽函數
只有工作對象Worker才是被moveToThread的,它的槽函數全是在子線程中運行的。為了避免使用主線程的engin注冊此對象【上面已分析出不可行】,所以定義了一個中間類:WorkerInterface,所有中間類是工作在main中的,手動調用其信號【如打開關閉】控制其工作對象對應的槽函數。
四、體會
1、Qt官方只是發布花里胡哨、功能強大的庫,至於怎么去用非常靈活,官方有時候也只是建議,沒有明確的答案。更多的需要自己去實踐、源碼剖析,得到適合自己的答案。
2、國內資源是真tm少,特別是國外新出的標准、技術,必要時,還是牆外的世界更精彩。
3、做自己認為對的事。
---------------------------------------------實踐-----------------------------------------------------
一、Myudp.h
#ifndef MYUDP_H
#define MYUDP_H #include <QObject> #include <QUdpSocket> #include <QThread> class Myudp : public QObject { Q_OBJECT public: explicit Myudp(QObject *parent = nullptr); ~Myudp(); signals: void rcvdDataSignal(const QByteArray&); void sendedSignal(const QString&);//發送成功 void getAString(QString str); private slots: void initSlot(); void requestSlot(); void sendSlot(const QByteArray&); void closeSlot(); private: QUdpSocket* udpClient = nullptr; const QString localIp="127.0.0.1"; const quint16 localPort=8080; const QString aimIp="127.0.0.1"; const quint16 aimPort=8888; private: }; class MyudpInterfase : public QObject { Q_OBJECT Q_PROPERTY(QByteArray dataBa MEMBER dataBa) public: MyudpInterfase() { udp.moveToThread(&udpThread); QObject::connect(&udp,SIGNAL(rcvdDataSignal(QByteArray)),this,SLOT(dataChangeSlot(QByteArray))); QObject::connect(this,SIGNAL(initSignal()),&udp,SLOT(initSlot())); QObject::connect(this,SIGNAL(sendSignal(QByteArray)),&udp,SLOT(sendSlot(QByteArray))); QObject::connect(this,SIGNAL(closeSiganl()),&udp,SLOT(closeSlot())); udpThread.start(); } ~MyudpInterfase() { udpThread.quit(); udpThread.wait(); } private slots: void dataChangeSlot(const QByteArray& ba) { dataBa = ba; emit dataChangeSignal(); } signals: void initSignal(); void sendSignal(const QByteArray&); void closeSiganl(); void dataChangeSignal(); private: Myudp udp; QThread udpThread; QByteArray dataBa; }; #endif // MYUDP_H
1.1、Myudp類就是簡單的C++中自定義類,應該最終被moveTothread
1.2、MyudpInterfase是用於QML訪問的中間類,所有的信號連接可放在構造函數中,並且定義控制Myudp的信號;注意在構造函數中退出線程
1.3、Q_PROPERTY訪問屬性,如果沒有訪問器【READ、WRITE】,則需要加MEMBER來指定這個Q_PROPERTY對應的是控制哪個成員變量。
二、Myudp.cpp
#include "myudp.h" Myudp::Myudp(QObject *parent) : QObject(parent) { } Myudp::~Myudp() { if(udpClient != nullptr) { qDebug() << "內存回收"; delete udpClient; udpClient = nullptr; } } /***********************************************/ // z 函數名稱:初始化 // h 函數作用:NULL // u 函數參數:NULL // x 函數返回值:NULL // y 備注:NULL /***********************************************/ void Myudp::initSlot() { if(udpClient == nullptr) { udpClient = new QUdpSocket(this); udpClient->bind(QHostAddress(localIp),localPort); QObject::connect(udpClient,SIGNAL(readyRead()),this,SLOT(requestSlot())); } } /***********************************************/ // z 函數名稱:接收數據 // h 函數作用:NULL // u 函數參數:NULL // x 函數返回值:NULL // y 備注:NULL /***********************************************/ void Myudp::requestSlot() { if(udpClient->pendingDatagramSize() == 0) { return; } QByteArray ba; ba.resize(udpClient->pendingDatagramSize()); QHostAddress tempHost(""); quint16 port = 0; udpClient->readDatagram(ba.data(),udpClient->pendingDatagramSize(),&tempHost,&port); emit rcvdDataSignal(ba); } /** *函數名:發送槽函數 *函數參數:NULL *函數作用:NULL *函數返回值:NULL *備注:NULL */ void Myudp::sendSlot(const QByteArray &info) { if(info.size()==udpClient->writeDatagram(info,QHostAddress(aimIp),aimPort)) { QString str = info.toHex().toUpper(); emit sendedSignal(str); } } /*****************************************************************/ //作者:朱小勇 //函數名稱:關閉 //函數參數:NULL //函數返回值:NULL //函數作用:NULL //備注:NULL /*****************************************************************/ void Myudp::closeSlot() { udpClient->close(); }
三、main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QThread> #include "myudp.h" #include <QQuickView> #include <QQmlContext> #include <QTimer> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); qmlRegisterType<MyudpInterfase>("Myudp.module",1,0,"Udp"); #if 1 QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/qml/my.qml"))); if (engine.rootObjects().isEmpty()) return -1; #endif return app.exec(); }
四、my.qml
import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.2 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 import QtQuick.Layouts 1.3 import Myudp.module 1.0 ApplicationWindow{ id: root visible: true width: Screen.width height: Screen.height title: qsTr("test") Component.onCompleted: { root.visibility = Window.Maximized } Udp { id: udp } TabView { anchors.fill: parent Tab { title: "UDP" Rectangle { GroupBox { id: group1 title: "數據接收" width: parent.width/2 height: parent.height flat: false TextEdit{ id: rcvTextEdit anchors.fill: parent anchors.margins: 5 } Connections { target: udp onDataChangeSignal: { rcvTextEdit.append(udp.dataBa.toString().toUpperCase()) } } } GroupBox{ id: group2 title: "數據發送" width: parent.width/2 height: parent.height/2 anchors.left: group1.right flat: false TextEdit{ id: sendTextEdit anchors.fill: parent anchors.margins: 5 } } Rectangle { width: parent.width/2 height: parent.height/2 anchors.top: group2.bottom anchors.left: group1.right Row { width: parent.width height: 30 spacing: 10 Button { text: qsTr("初始化") onClicked: { udp.initSignal() } } Button { text: qsTr("發送") onClicked: { udp.sendSignal(sendTextEdit.text) } } Button { text: qsTr("退出") onClicked: { udp.closeSiganl() Qt.quit() } } } } } } Tab { title: "Blue" Rectangle { color: "blue" } } Tab { title: "Green" Rectangle { color: "green" } } } }
4.1、注意上面的Connections
由於udp是全局的,而rcvTextEdit是多層控件樹下的,所以Connections的位置不能隨意寫,剛開始我把Connections放在了和udp一個層級,注意這樣就訪問不到rcvTextEdit了。
