我正在编写一个演示应用程序来简化我的QT学习曲线。我的目标是更新从后台运行的线程中的值作为数据生成器。我编写了QML并使用QT标准数据绑定方法(即Q_Property)将C ++成员绑定到它。目前该解决方案按预期工作,但想确认这是否是实现相同的正确方法。
观
查询
我的目标是保持数据生成器类解耦。
最后代码
//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();
}
};
答案 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::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);
}
}