![](/img/trans.png)
[英]How to access Q_PROPERTY of QStringlist from Q_PROPERTY of MyClass * in qml?
[英]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.