返回在不同线程

时间:2015-09-06 12:35:22

标签: c++ multithreading thread-safety qtquick2

我是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作为属性的简单容器。控制器包含属性数据并启动一个线程,该线程定期更新数据并作为信号发出更改。输出将正确显示,但返回原始指针感觉非常不安全。所以我的问题是:

  1. 当QML使用指针更新视觉效果时,如果数据写得太快并且线程同时操纵数据,会发生什么?
  2. 是否有返回原始指针的替代方法,例如Qt为此目的提供的东西以及我还没有找到的东西?
  3. 在使用Qt / QML时,我的想法是错误的吗?我首先开发了C ++后端(没有任何Qt部分),现在我正在尝试将它连接到GUI。也许我应该从一开始就更好地设计Qt或QML友好的后端?

1 个答案:

答案 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对象的实例,并注册信号以获取并分配最新的副本。