我是Qt / QtQuick的新手,我必须开发一个应用程序,它使用一些传感器数据,这些数据通过网络定期在不同的线程中接收。这些数据应该在c ++中用于计算,最新的数据也应该用QML显示。通过使用互斥锁进行保护,所有内容都设置为在c ++内部是线程安全的,并且数据在QML内部可见。 但是,我对QML方面的线程安全有一些担忧,我无法在网上找到有关此主题的信息或示例。具体来说,我担心返回一个指针(这是将C ++对象返回到QML的唯一方法)而不是值,因此是对象的副本。这是一个证明问题的最小例子:
// File data.h
#include <QObject>
class Data : public QObject {
Q_OBJECT
Q_PROPERTY(QString someData READ someData WRITE setSomeData NOTIFY someDataChanged)
public:
explicit Data(QObject* parent = nullptr)
:QObject(parent)
{
}
QString someData() const {
return _someData;
}
void setSomeData(const QString& value) {
if (_someData != value) {
_someData = value;
emit someDataChanged();
}
}
signals:
void someDataChanged();
private:
QString _someData;
}; // Data
// File: controller.h
#include <QObject>
#include <thread>
class Controller : public QObject {
Q_OBJECT
Q_PROPERTY(Data data READ data NOTIFY dataChanged)
public:
explicit Controller(QObject* parent = nullptr)
:QObject(parent)
,_running(false)
,_data(nullptr)
{
_data = new Data();
}
virtual ~Controller() {
delete _data;
}
void start() {
_running = true;
_thread = std::thread([this]() { _threadFunc(); });
}
void stop() {
_running = false;
if (_thread.joinable()) {
_thread.join();
}
}
Data* data() {
return _data;
}
signals:
void dataChanged();
private:
void _threadFunc() {
while (_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
_data.setSomeData("foo");
emit dataChanged();
}
}
bool _running;
std::thread _thread;
Data* _data;
}; // Controller
// File main.qml
import QtQuick 2.0
Rectangle {
width: 100
height: 100
Text {
anchors.centerIn: parent
text: Controller.data.someData
}
}
Data是一个将QString作为属性的简单容器。控制器包含属性数据并启动一个线程,该线程定期更新数据并作为信号发出更改。输出将正确显示,但返回原始指针感觉非常不安全。所以我的问题是:
答案 0 :(得分:2)
好吧,我认为我找到了解决问题的方法:我仍然确定处理同一个对象会导致问题。我读了一些关于QML所有权的内容,发现通过使用Property,所有权仍然在C ++方面。通过使用返回指针的函数,QML将接管所有权,并将在以后删除该对象。所以我在这里所做的就是如果有人在某一天遇到同样的问题,那就是:
// File data.h
#include <QObject>
class Data : public QObject {
Q_OBJECT
Q_PROPERTY(QString someData READ someData WRITE setSomeData NOTIFY someDataChanged)
public:
explicit Data(QObject* parent = nullptr)
:QObject(parent)
{
}
Data(const Data& data)
:QObject(data.parent)
,_someData(data.someData)
{
}
QString someData() const {
return _someData;
}
void setSomeData(const QString& value) {
if (_someData != value) {
_someData = value;
emit someDataChanged();
}
}
signals:
void someDataChanged();
private:
QString _someData;
}; // Data
// File: controller.h
#include <QObject>
#include <thread>
#include <mutex> // New
class Controller : public QObject {
Q_OBJECT
//Q_PROPERTY(Data data READ data NOTIFY dataChanged) // Removed
public:
explicit Controller(QObject* parent = nullptr)
:QObject(parent)
,_running(false)
,_data(nullptr)
{
_data = new Data();
}
virtual ~Controller() {
delete _data;
}
void start() {
_running = true;
_thread = std::thread([this]() { _threadFunc(); });
}
void stop() {
_running = false;
if (_thread.joinable()) {
_thread.join();
}
}
Q_INVOKABLE Data* data() { // Modified to be an invokable function instead of a property getter
std::lock_guard<std::mutex> lock(_mutex); // New
return new Data(*_data); // New
}
signals:
void dataChanged();
private:
void _threadFunc() {
while (_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::lock_guard<std::mutex> lock(_mutex); // New
_data.setSomeData("foo");
emit dataChanged();
}
}
bool _running;
std::thread _thread;
std::mutex _mutex; // New
Data* _data;
}; // Controller
// File main.qml
// NOTE: Controller is registered as context property alias 'controller'
import QtQuick 2.0
// Import the datatype 'data' to be used in QML
//import ...
Rectangle {
id: myRect
width: 100
height: 100
property Data data
Connections {
target: controller
onDataChanged: {
myRect.data = controller.data()
}
}
Text {
anchors.centerIn: parent
text: data.someData
}
}
基本上,我确保锁定对象并复制。然后,QML可以安全地使用此副本,并且QML引擎将在使用后注意删除内存。此外,我在QML中创建了一个Data对象的实例,并注册信号以获取并分配最新的副本。