【问题标题】:Is it the correct way to update QML from thread using multi layer signals?使用多层信号从线程更新 QML 是否正确?
【发布时间】:2018-02-11 10:32:46
【问题描述】:

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

想法

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

【问题讨论】:

    标签: qt qml qt-quick


    【解决方案1】:

    关于总体设计:

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

    但是,您可以生成数据,但不是最佳的。使用QThread::sleep(1),您将阻塞事件循环,从而无法优雅地停止线程。相反,您应该使用计时器。计时器和 DemoData 类仍将在该线程上运行,但使用计时器和事件循环。这样 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。然而,这只适用于事件循环正在运行的 QThread(因此需要一个计时器)。

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

    如果您的线程没有正确运行事件循环,您可以使用中断请求。不要退出,而是致电QThread::requestInterruption。在您的generateData 方法中,您必须使用较短的间隔并每次检查QThread::isInterruptionRequested

    void generateData()
    {
        int hrValIndex = 0, spo2ValIndex = 0, respValIndex = 0, co2ValIndex = 0;
    
        while(!QThread::currentThread()->isInterruptionRequested()) {
            // code...
            QThread::sleep(1);
        }
    }
    

    【讨论】:

    • 最初我选择了计时器并转向线程以了解有关 QT 线程的更多信息。我来自 Win32/MFC/C# 背景,所以在 QT 中尝试所有可能的事情。是的 getData 应该更改为与 setter 相关的名称,这将更正。最后一个快速的问题。在应用程序退出时优雅地关闭线程的正确方法是什么?我可以通过中断检查将一秒钟的睡眠分成最短的毫秒。正在尝试连接 aboutToQuit() 插槽,但这对我没有帮助。
    • 甚至 QThreads 也有自己的事件循环。我会相应地更新答案
    • 如果我理解正确,我需要输入while(!QThread::currentThread()-&gt;isInterruptionRequested()) 来捕捉中断并在aboutToQuit 信号上触发中断。即QObject::connect(qApp, &amp;QCoreApplication::aboutToQuit, &amp;thread, [&amp;thread](){ thread.requestInterruption(); thread.wait(1000); });
    • 正确。但是让等待时间比你的循环超时(即1100)长一点。如果你想无限等待,你可以不考虑超时。
    • 如果你重新实现QThread::run,不需要退出,因为没有事件循环在运行。但是对于您的情况,需要qutting。但是,您也应该从主线程调用 quit,因为 QThread 就在那里!要么使用QMetaObject::invokeMethod
    猜你喜欢
    • 2023-04-06
    • 2018-04-11
    • 1970-01-01
    • 2020-05-07
    • 2018-07-25
    • 2019-07-28
    • 2011-02-05
    • 2020-06-24
    • 2011-03-28
    相关资源
    最近更新 更多