繁体   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