这是使用多层信号从线程更新QML的正确方法吗?

时间:2017-09-02 16:23:58

标签: qt qml qt-quick

我正在编写一个演示应用程序来简化我的QT学习曲线。我的目标是更新从后台运行的线程中的值作为数据生成器。我编写了QML并使用QT标准数据绑定方法(即Q_Property)将C ++成员绑定到它。目前该解决方案按预期工作,但想确认这是否是实现相同的正确方法。

  1. 在线程中生成数据(类DemoData)
  2. 发出信号通知另一个班级(班级VitalData)
  3. 发出Q_Property信号(来自VitalData类)以更新UI
  4. 查询

    1. 我应该生成数据并通知UI有关单个类中的更改并将该类实例发送到新线程吗?在这种情况下,我可以使用单个信号来更新UI。
    2. 根据目前的设计,它会受到性能不佳的影响,或者在最坏的情况下,由于快速信号插槽,UI部分可能会遗漏一些数据?
    3. 我的目标是保持数据生成器类解耦。

      最后代码

      //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();
          }
      };
      

1 个答案:

答案 0 :(得分:3)

关于一般设计:

对我来说很好看。就个人而言,我总是在其他所有事情之前运行moveToThread,但这不应该影响这种情况下的结果。 (唯一令人困惑的是你将方法命名为getData。它是一个不是getter的setter,应该相应地命名)

但是,您可以生成数据,但不是最佳数据。使用QThread::sleep(1),您将阻止事件循环,从而无法正常停止线程。相反,你应该使用计时器。计时器和DemoData类仍将在该线程上运行,但是使用timer和eventloop。这样QThread仍然可以接收事件等(例如,如果您以后需要将数据发送到您的类,您可以使用一个插槽,但只有,如果线程的eventloop可以运行):

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);
    }
}