2015-09-06 17 views
3

我對Qt/QtQuick相當陌生,我必須開發一個應用程序,它使用一些傳感器數據,這些數據通過網絡在不同線程中定期接收。這些數據應該在C++中用於計算,最新的數據也應該用QML顯示。通過使用互斥體進行保護,數據在QML中可見地更新,所有東西都設置爲在C++內部是線程安全的。 但是,我對QML方面的線程安全性有一些擔憂,我無法在Web上找到關於此主題的信息或示例。具體來說,我關心的是返回一個指針(這是將C++對象返回給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 
    } 
} 

數據是一個容納QString屬性的簡單容器。控制器包含屬性數據並啓動一個線程,該線程定期更新數據併發出變化信號。輸出將正確顯示,但返回原始指針感覺非常不安全。所以在這裏我的問題是:

  1. 如果數據寫入速度過快,當QML使用指針來更新視覺線程同時操縱數據會發生什麼?
  2. 是否有其他方法可以返回原始指針,例如Qt爲此目的提供的東西以及我還沒有找到的東西?
  3. 在使用Qt/QML時,我的思維錯誤嗎?我首先開發了C++後端(沒有任何Qt部分),現在我試圖將它連接到GUI。也許我應該從開始分別設計Qt或QML友好的後端?

回答

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對象的一個​​實例,並註冊信號以獲取並分配最新的副本。