簡體   English   中英

從另一個線程訪問 qml 中的 Q_PROPERTY 的最佳實踐

[英]Best practice accessing Q_PROPERTY in qml from another thread

我有一個更大的項目,幾個小時后隨機崩潰。 我相信這是由於 qml 訪問了我的 C++ QObject 中的Q_PROPERTY ,其中Q_PROPERTY設置在另一個線程中,導致數據競爭。

我在這里有一個簡單的玩具項目。 其中main.qml有一個 label 可以訪問 QProperty test.testChild.time 當 QTimer 過期並調用Test::timerExpired()時, test.testChild.time在另一個線程中設置。 Test::timerExpired()有一個QThread::usleep(100000)來模擬長時間的操作。 當我評論下面的moveToThread(&m_workerThread)行時,用戶界面的響應速度比以前慢得多,但一切都在同一個線程中,所以我相信不會發生數據競爭。 如果我不評論moveToThread(&m_workerThread)並且有timer.setInterval(0) ,那么 UI 響應非常靈敏,但我的 Ubuntu VM 上通常會在幾分鍾內發生崩潰。

下面的代碼是我的簡單玩具項目中test.cpp的一部分

Test::Test(QObject *parent) : QObject(parent)
//  , testChild(this) // uncommenting this line will cause testChild to move to m_workerThread
{
    uiThread = QThread::currentThreadId();

    // move this object to a separate thread so does not interrupt UI thread
    moveToThread(&m_workerThread); // commenting this line will make test::timerExpired() run on UI thread and slow down UI

    timer.setInterval(0); // with interval 0, more likely to seg fault due to data race

    connect(&timer, SIGNAL(timeout()), this, SLOT(timerExpired()));
    connect(&m_workerThread, SIGNAL(started()), &timer, SLOT(start()));
    m_workerThread.start();
}

void Test::timerExpired()
{
    if (uiThread == QThread::currentThreadId())
        qCritical() << "timerExpired() Controller thread is same as UI thread";

    QMutexLocker locker(&mutex);// prevent UI thread from accessing objects below while they update with a QMutexLocker

    QString time;

    time = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");

    QThread::usleep(100000); // long operation

    setTime(time);
    testChild.setTime(time);
}

如何安全地擁有兩個線程,一個用於 qml,另一個用於長操作,而 qml 可以訪問在長操作線程中更新的 QProperties?

我相信我已經使用 model 解決了這個問題,因此 QML 可以安全地與另一個線程通信。

model 在 UI 線程中實例化。 model 使用來自另一個線程(這是線程安全的)的信號和槽接收數據。 model 具有 QML 可以與之交互的 QProperties。 當 model 從另一個線程接收到新數據時,QProperties 被更新並且 QML 接收到新數據。

該項目在這里,但也包括在下面

TestQPropertyThread.pro

QT += quick

CONFIG += c++11

DEFINES += QT_DEPRECATED_WARNINGS

QMAKE_CXXFLAGS += -g -Wall -Werror

SOURCES += \
        controller.cpp \
        controllerchild.cpp \
        main.cpp \
        model.cpp

RESOURCES += qml.qrc

HEADERS += \
    controller.h \
    controllerchild.h \
    model.h

主文件

#include "controller.h"
#include "model.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    Controller test;
    Model model(test);

    engine.rootContext()->setContextProperty(QStringLiteral("model"), &model);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

controller.cpp

#include "controller.h"

Controller::Controller(QObject *parent) : QObject(parent), testChild(this), timer(this) // testChild to move to m_workerThread
{
    uiThread = QThread::currentThreadId();

    // move this object to a separate thread so does not interrupt UI thread
    moveToThread(&m_workerThread); // commenting this line will make test::timerExpired() run on UI thread and slow down UI

    timer.setInterval(0); // with interval 0, more likely to seg fault due to data race

    connect(&timer, SIGNAL(timeout()), this, SLOT(timerExpired()));
    connect(&m_workerThread, SIGNAL(started()), &timer, SLOT(start()));
    m_workerThread.start();
}

void Controller::timerExpired()
{
    if (uiThread == QThread::currentThreadId())
        qCritical() << "Controller::timerExpired() Controller thread is same as UI thread";

    // prevent UI thread from accessing objects below while they update with a QMutexLocker
    QMutexLocker locker(&mutex);

    QString time;

    time = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
    qDebug() << "Controller::timerExpired()" << time; // this can impact preformance when usleep is commented out
    QThread::usleep(100000); // long operation

    testChild.setTime(time);
}

controller.h

#ifndef TEST_H
#define TEST_H

#include <QObject>
#include <QTimer>
#include <QThread>
#include <QMutex>
#include <QDebug>
#include <QDateTime>
#include "controllerchild.h"

class Controller : public QObject
{
    Q_OBJECT

public:
    explicit Controller(QObject *parent = nullptr);

    ControllerChild testChild;

public slots:
    void timerExpired();

private:
    QTimer timer;
    QThread m_workerThread;
    Qt::HANDLE uiThread;
    QMutex mutex;
};

#endif // TEST_H

控制器孩子.cpp

#include "controllerchild.h"

ControllerChild::ControllerChild(QObject *parent) : QObject(parent)
{

}

void ControllerChild::setTime(const QString &value)
{
    time = value;
    emit timeChanged(time);
}

void ControllerChild::onButtonPress(const QString &value)
{
    qDebug() << "ControllerChild::onButtonPress()" << value << QThread::currentThreadId() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
    emit onBoolChanged(value); // send value back to UI label
}

控制器孩子.h

#ifndef TESTCHILD_H
#define TESTCHILD_H

#include <QObject>
#include <QDebug>
#include <QThread>
#include <QDateTime>

class ControllerChild : public QObject
{
    Q_OBJECT

public:
    explicit ControllerChild(QObject *parent = nullptr);

    void setTime(const QString &value);

public slots:
    void onButtonPress(const QString &value);

signals:
    void timeChanged(const QString &value);
    void onBoolChanged(QString value);

private:
    QString time;

};

#endif // TESTCHILD_H

model.cpp

#include "model.h"

Model::Model(Controller &test, QObject *parent) : QObject(parent), test(test)
{
    connect(&test.testChild, &ControllerChild::timeChanged, this, [=](const QString &time){ this->setTime(time); });
    connect(this, &Model::onButtonPressChanged, &test.testChild, &ControllerChild::onButtonPress);
    connect(&test.testChild, &ControllerChild::onBoolChanged, this, [=](const QString &value){ this->setBoolValue(value); });
}

QString Model::getTime() const
{
    //    return QString("test Child %1").arg(time);
    return time;
}

void Model::setTime(const QString &value)
{
    time = value;
    emit timeChanged();
}

void Model::onButtonPress(const QString &value)
{
    qDebug() << "Model::onButtonPress()" << value << QThread::currentThreadId() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
    emit onButtonPressChanged(value);
}

void Model::onBool(const QString &value)
{
    qDebug() << "Model::onBool()" << value << QThread::currentThreadId() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
    emit onBoolChanged(value);
}

void Model::setBoolValue(const QString &value)
{
    boolValue = value;
    qDebug() << "Model::setBoolValue()" << value << QThread::currentThreadId() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
    emit onBoolChanged(value);
}

model.h

#ifndef MODEL_H
#define MODEL_H

#include <QObject>

#include "controller.h"

class Model : public QObject
{
    Q_OBJECT

    Q_PROPERTY(QString time READ getTime WRITE setTime NOTIFY timeChanged)
    Q_PROPERTY(QString boolValue MEMBER boolValue NOTIFY onBoolChanged)
public:
    explicit Model(Controller &test, QObject *parent = nullptr);

    QString getTime() const;
    void setTime(const QString &value);

    void setBoolValue(const QString &value);

public slots:
    void onButtonPress(const QString &value);
    void onBool(const QString &value);

signals:
    void timeChanged();
    void onButtonPressChanged(const QString &value);
    void onBoolChanged(const QString &value);

private:
    QString time;
    Controller &test;
    QString boolValue;
};

#endif // MODEL_H

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12

import QtQuick.Controls 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Label {
        id: time1
        text: model.time
    }

    BusyIndicator {
        id: busyIndicator
        x: 0
        y: 23
    }

    PathView {
        id: pathView
        x: 0
        y: 135
        width: 640
        height: 130
        delegate: Column {
            spacing: 5
            Rectangle {
                width: 40
                height: 40
                color: colorCode
                anchors.horizontalCenter: parent.horizontalCenter
            }

            Text {
                x: 5
                text: name
                anchors.horizontalCenter: parent.horizontalCenter
                font.bold: true
            }
        }
        path: Path {
            startY: 100
            startX: 120
            PathQuad {
                x: 120
                y: 25
                controlY: 75
                controlX: 260
            }

            PathQuad {
                x: 120
                y: 100
                controlY: 75
                controlX: -20
            }
        }
        model: ListModel {
            ListElement {
                name: "Grey"
                colorCode: "grey"
            }

            ListElement {
                name: "Red"
                colorCode: "red"
            }

            ListElement {
                name: "Blue"
                colorCode: "blue"
            }

            ListElement {
                name: "Green"
                colorCode: "green"
            }
        }
    }

    Button {
        id: button
        x: 0
        y: 271
        text: qsTr("Press me!!")
        checkable: true
        onToggled: model.onButtonPress(button.checked)
    }

    Label {
        id: label
        x: 106
        y: 294
        text: model.boolValue
    }


}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM