2017-09-02 181 views
1

我正在寫一個演示應用程序來緩解我的QT學習曲線。我的目標是更新來自作爲數據生成器在後臺運行的線程的值。我編寫了QML,並使用QT標準數據綁定方法(即Q_Property)將C++成員綁定到它。目前該解決方案按預期工作,但希望確認這是否是實施相同的正確方法。使用多層信號從線程更新QML是否正確?

思想

  1. 在一個線程(類DemoData)生成數據
  2. 發射信號,以通知另一個類(類VitalData)
  3. 發光Q_PROPERTY信號(來自類VitalData)來更新UI

查詢

  1. 我應該生成數據並通知UI有關單個類中的更改並將該類實例發送到新線程嗎?在這種情況下,我可以使用單個信號來更新UI。
  2. 基於目前的設計是否會受到糟糕的性能影響,或者在最糟糕的情況下,由於快速信號時隙,UI部分可能會遺漏一些數據?

我的目標是保持數據生成器類的解耦。

最後的代碼

//A data generator class - this can be altered by some other class if neccessary 
class DemoData : public QObject 
{ 
    Q_OBJECT 
    int nextUpdateIndex = 0; 

public slots: 
    void generateData() 
    { 
     int hrValIndex = 0, spo2ValIndex = 0, respValIndex = 0, co2ValIndex = 0; 

     while(true) { 
      switch(nextUpdateIndex) { 
      case 0: 
       emit valueUpdated(nextUpdateIndex, demoHRRates[hrValIndex]); 
       if(hrValIndex == ((sizeof demoHRRates)/(sizeof(int))) - 1) 
        hrValIndex = 0; 
       else 
        hrValIndex++; 
       nextUpdateIndex = 1; 
       break; 
      } 
      QThread::sleep(1); 
     } 
    } 
signals: 
    //Signal to notify the UI about new value 
    void valueUpdated(int index, int data); 
}; 


//Class to interact with QML UI layer. This class only hold properties and it's binding 
class VitalData : public QObject 
{ 
    Q_OBJECT 
    Q_PROPERTY(int hrRate READ getHrRate NOTIFY hrRateChanged) 

    public: 
    int getHrRate() const { 
     return m_hrRate; 
    } 

public slots: 
    void getData(int index, int value) 
    { 
     switch(index){ 
     case 0: 
      m_hrRate = value; 
      emit hrRateChanged(); 
      break; 
     } 
    } 

signals: 
    //This signal actually notifies QML to update it value 
    void hrRateChanged(); 
}; 

int main() 
{ 
    QGuiApplication app(argc, argv); 

    //Data generator class is getting linked with UI data feeder class 
    VitalData med; 
    DemoData demo; 
    QObject::connect(&demo, SIGNAL(valueUpdated(int, int)), &med, SLOT(getData(int, int))); 

    //Standard way to launch QML view 
    QQuickView view; 
    view.rootContext()->setContextProperty("med", &med); 
    view.setSource(QUrl(QStringLiteral("qrc:/main.qml"))); 
    view.show(); 

    //Moving data generator to a background thread 
    QThread thread; 
    demo.moveToThread(&thread); 
    QObject::connect(&thread, SIGNAL(started()), &demo, SLOT(generateData())); 
    thread.start(); 

    return app.exec(); 
} 

爲線程退出

int main() 
{ 
    QThread thread; 
    demo.moveToThread(&thread); 
    QObject::connect(&thread, SIGNAL(started()), &demo, SLOT(generateData())); 
    QObject::connect(qApp, &QCoreApplication::aboutToQuit, &thread, [&thread](){ 
     thread.requestInterruption(); 
     thread.wait(); 
    }); 
    thread.start(); 
} 


class DemoData : public QObject 
{ 
    Q_OBJECT 
public slots: 
    void generateData() 
    { 
     while(!QThread::currentThread()->isInterruptionRequested()) { 
      switch(nextUpdateIndex) { 
       case 0: 
       break; 
      } 
      QThread::msleep(200); 
      qDebug() << "Thread running.."; 
     } 

     //This quit was necessary. Otherwise even with requestInterruption call thread was not closing though the above debug log stopped 
     QThread::currentThread()->quit(); 
    } 
}; 

回答

3

新的代碼對於廣大的設計:

我看不錯。就個人而言,我總是在運行之前運行moveToThread,但這不應該影響這種情況下的結果。 (唯一令人困惑的是,您將方法命名爲getData,它不是一個吸氣器,應該相應命名)

但是,您的數據生成是可能的,但不是最優的。使用QThread::sleep(1),您將阻止事件回調,從而無法優雅地停止線程。相反,你應該使用一個計時器。定時器和DemoData類仍然在該線程上運行,但是通過使用timer和eventloop。這樣的的QThread仍然可以接收事件等(例如,如果你需要數據後發送到你的類,你可以使用一個插槽,但只,如果線程的事件循環可以運行):

class DemoData : public QObject 
{ 
    Q_OBJECT 
    int nextUpdateIndex = 0; 

public slots: 
    void generateData() 
    { 
     auto timer = new QTimer(this); 
     connect(timer, &QTimer::timeout, this, &DemoData::generate); 
     timer->start(1000); 
    } 

private slots: 
    void generate() 
    { 
     //code to generate data here, without the loop 
     //as this method gets called every second by the timer 
    } 
}; 

還有一種方法,如果你不想使用定時器。您必須重新實現QThread並自己處理事件,但只有在沒有其他選擇時才應該這樣做。您將不得不覆蓋QThread::run

優雅地退出線程相當容易,但取決於線程的構建方式。如果您有一個有效的事件回覆,即沒有長時間阻塞操作,則可以簡單地撥打QThread::quitQThread::wait。然而,這隻適用於Eventloop正在運行的QThread(因此需要定時器)。

QObject::connect(qApp, &QCoreApplication::aboutToQuit, &thread, [&thread](){ 
    thread.quit(); 
    thread.wait(5000); 
}); 

如果您的線程沒有正確運行eventloop,則可以使用中斷請求。不要退出,請致電QThread::requestInterruption。在你generateData方法,你就必須用短的時間間隔,並檢查QThread::isInterruptionRequested每次:

void generateData() 
{ 
    int hrValIndex = 0, spo2ValIndex = 0, respValIndex = 0, co2ValIndex = 0; 

    while(!QThread::currentThread()->isInterruptionRequested()) { 
     // code... 
     QThread::sleep(1); 
    } 
} 
+0

那麼最初我去定時器,轉移到線程詳細瞭解QT線程。我來自Win32/MFC/C#的背景,所以在QT中嘗試所有可能的東西。是的'getData'應該改成與setter相關的名字,將會改正。最後一個簡短的問題。在app退出時優雅地關閉線程的正確方法是什麼?我可以通過休息檢查將一秒鐘的睡眠分成最短的毫秒。正在嘗試連接'aboutToQuit()'插槽,但這並沒有幫助我。 – Anup

+1

即使QThreads有他們自己的事件循環。我會更新相應的答案 – Felix

+0

如果我正確理解了你的話,我需要把'while(!QThread :: currentThread() - > isInterruptionRequested())'趕上中斷並激發'aboutToQuit'信號的中斷。即'QObject :: connect(qApp,&QCoreApplication :: aboutToQuit,&thread,[&thread]()thread.requestInterruption(); thread.wait(1000); });' – Anup