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

Is it the correct way to update QML from thread using multi layer signals?

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

想法

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

查询

  1. 我是否应该生成数据并通知 UI 关于单个 class 的更改并将该 class 实例发送到新线程?因为在这种情况下我可以使用单个信号来更新 UI。
  2. 根据当前的设计,它是否会遭受性能不佳的困扰,或者在最坏的情况下,由于快速信号槽,某些数据可能会在 UI 部分丢失?

我的目标是保持数据生成器 class 解耦。

代码终于出来了

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

关于总体设计:

我觉得不错。就个人而言,我总是 运行 moveToThread 优先于其他一切,但这不应该影响这种情况下的结果。 (唯一令人困惑的是您将方法命名为 getData。它是 setter 而不是 getter,应该相应地命名)

但是,您的数据的生成是可能的,但不是最佳的。使用 QThread::sleep(1) 您将阻塞事件循环,从而无法正常停止线程。相反,您应该使用计时器。计时器和 DemoData class 仍将在该线程上 运行ning,但通过使用计时器和事件循环。这样 QThread 仍然可以接收事件等。(例如,如果您稍后需要向您的 class 发送数据,您可以使用插槽,但前提是线程的事件循环可以 运行 ):

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::quit and QThread::wait。然而,这仅适用于事件循环为 运行ning 的 QThread(因此需要计时器)。

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

如果你的线程没有运行事件循环正常,你可以使用中断请求。不是退出,而是每次调用 QThread::requestInterruption. In your generateData method, you then have to use short intervals and check QThread::isInterruptionRequested

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

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