【问题标题】:Best practice accessing Q_PROPERTY in qml from another thread从另一个线程访问 qml 中的 Q_PROPERTY 的最佳实践
【发布时间】:2020-10-20 21:30:34
【问题描述】:

我有一个较大的项目,几个小时后随机崩溃。我相信这是由于 qml 在我的 C++ QObject 中访问Q_PROPERTY,其中Q_PROPERTY 设置在另一个线程中,导致数据竞争。

我有一个简单的玩具项目here。其中main.qml 有一个访问QProperty test.testChild.time 的标签。当 QTimer 过期并调用 Test::timerExpired() 时,test.testChild.time 在另一个线程中设置。 Test::timerExpired() 有一个 QThread::usleep(100000) 来模拟长时间操作。当我评论下面的moveToThread(&m_workerThread) 行时,ui 的响应速度比以前慢了很多,但一切都在同一个线程中,所以我相信不会发生数据竞争。如果我不评论 moveToThread(&m_workerThread) 并拥有 timer.setInterval(0),则 UI 响应速度非常快,但我的 Ubuntu VM 上通常会在几分钟内发生崩溃。

下面的代码是我的简单玩具项目here中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?

【问题讨论】:

  • 代码不完整。 setTime()testChild 是什么?您如何访问 QML?
  • 用这个测试程序重现问题并不容易。它只发生在我身上一次,15分钟后。也不知道为什么它会崩溃,因为我可以看到存在大量内存泄漏 - 杀死核心转储文件后,应用程序运行的时间越长,文件的大小就越大。同样在 valgrind 下运行显示仍然可以访问大量内存。这可能是由于计时器淹没了插槽 - 计时器立即到期并且插槽为 100000+us 工作。要修复可疑的竞争条件,您可以尝试通过向插槽发出信号来设置测试时间,这将在 UI 线程中执行。
  • @folibis 我已经更清楚地描述了可以找到其余代码的位置。我认为如果其他代码下载我的简单项目而不是问题中有大量代码,可能会更容易。
  • @doqtor 这正是我遇到的问题,我发现它很难重现,除非我将计时器间隔设置为 0。我认为你是对的,我需要发出一个信号到 UI 线程中的一个类。 UI 线程中的此类将保存 QProperties,因此不会发生数据竞争(据我所知)。
  • @doqtor 有人描述了向 UI 中的第二类发送信号 here

标签: c++ multithreading qt qml thread-safety


【解决方案1】:

我相信我已经使用模型解决了这个问题,因此 QML 可以安全地与另一个线程通信。

模型在 UI 线程中实例化。该模型使用来自另一个线程(这是线程安全的)的信号和槽接收数据。该模型具有 QML 可以与之交互的 QProperties。当模型从另一个线程接收到新数据时,QProperties 被更新并且 QML 接收到新数据。

该项目是here,但也包含在下面

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

main.cpp

#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();
}

控制器.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);
}

控制器.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

controllerchild.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
}

controllerchild.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

模型.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);
}

模型.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
    }


}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 2022-01-23
    • 2012-01-15
    • 2012-03-23
    • 2010-10-14
    • 2023-04-09
    • 1970-01-01
    相关资源
    最近更新 更多