使用多层信号从线程更新 QML 是正确的方法吗?
Is it the correct way to update QML from thread using multi layer signals?
我正在编写一个演示应用程序来缓解我的 QT 学习曲线。我的目标是从后台 运行 作为数据生成器的线程更新值。我编写了 QML 并使用 QT 标准数据绑定方法将 C++ 成员绑定到它,即 Q_Property。目前该解决方案按预期工作,但想确认这是否是实现该解决方案的正确方法。
想法
- 在线程中生成数据(class DemoData)
- 发出信号通知另一个class(class VitalData)
- 发出 Q_Property 信号(来自 class VitalData)以更新 UI
查询
- 我是否应该生成数据并通知 UI 关于单个 class 的更改并将该 class 实例发送到新线程?因为在这种情况下我可以使用单个信号来更新 UI。
- 根据当前的设计,它是否会遭受性能不佳的困扰,或者在最坏的情况下,由于快速信号槽,某些数据可能会在 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);
}
}
我正在编写一个演示应用程序来缓解我的 QT 学习曲线。我的目标是从后台 运行 作为数据生成器的线程更新值。我编写了 QML 并使用 QT 标准数据绑定方法将 C++ 成员绑定到它,即 Q_Property。目前该解决方案按预期工作,但想确认这是否是实现该解决方案的正确方法。
想法
- 在线程中生成数据(class DemoData)
- 发出信号通知另一个class(class VitalData)
- 发出 Q_Property 信号(来自 class VitalData)以更新 UI
查询
- 我是否应该生成数据并通知 UI 关于单个 class 的更改并将该 class 实例发送到新线程?因为在这种情况下我可以使用单个信号来更新 UI。
- 根据当前的设计,它是否会遭受性能不佳的困扰,或者在最坏的情况下,由于快速信号槽,某些数据可能会在 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);
}
}