使用多层信号从线程更新QML是否正确?
我正在写一个演示应用程序来缓解我的QT学习曲线。我的目标是更新来自作为数据生成器在后台运行的线程的值。我编写了QML,并使用QT标准数据绑定方法(即Q_Property)将C++成员绑定到它。目前该解决方案按预期工作,但希望确认这是否是实施相同的正确方法。使用多层信号从线程更新QML是否正确?
思想
- 在一个线程(类DemoData)生成数据
- 发射信号,以通知另一个类(类VitalData)
- 发光Q_PROPERTY信号(来自类VitalData)来更新UI
查询
- 我应该生成数据并通知UI有关单个类中的更改并将该类实例发送到新线程吗?在这种情况下,我可以使用单个信号来更新UI。
- 基于目前的设计是否会受到糟糕的性能影响,或者在最糟糕的情况下,由于快速信号时隙,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();
}
};
新的代码对于广大的设计:
我看不错。就个人而言,我总是在运行之前运行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::quit
和QThread::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);
}
}
那么最初我去定时器,转移到线程详细了解QT线程。我来自Win32/MFC/C#的背景,所以在QT中尝试所有可能的东西。是的'getData'应该改成与setter相关的名字,将会改正。最后一个简短的问题。在app退出时优雅地关闭线程的正确方法是什么?我可以通过休息检查将一秒钟的睡眠分成最短的毫秒。正在尝试连接'aboutToQuit()'插槽,但这并没有帮助我。 – Anup
即使QThreads有他们自己的事件循环。我会更新相应的答案 – Felix
如果我正确理解了你的话,我需要把'while(!QThread :: currentThread() - > isInterruptionRequested())'赶上中断并激发'aboutToQuit'信号的中断。即'QObject :: connect(qApp,&QCoreApplication :: aboutToQuit,&thread,[&thread]()thread.requestInterruption(); thread.wait(1000); });' – Anup