我试图通过每隔500毫秒调用一个线程中的updateGUI
函数来更新主窗口。除非我关闭窗口,否则会显示窗口但不会使用新值更新。当我这样做时,会打开一个带有新值的新窗口。我找到了this question,但它没有回答我的问题。我知道(如qt文档中所述)
QApplication::exec
进入主事件循环并等待直到exit()
被称为。{/ p>
我尝试使用processEvents()
,但主窗口反复打开和关闭,非常快,我甚至看不到它。这是我的代码:
float distanceToObject;
bool objectDetected;
Modes currentMode;
void timerStart(std::function<void(void)> func, unsigned int interval)
{
std::thread([func, interval]()
{
while (true)
{
auto x = std::chrono::steady_clock::now() + std::chrono::milliseconds(interval);
func();
std::this_thread::sleep_until(x);
}
}).detach();
}
int updateGUI(void)
{
int argc = 0;
char **argv = NULL;
QApplication a(argc, argv);
MainWindow w;
// Set text of a label
w.setDistance(QString::number(distanceToObject));
// Also update objectDetected and currentMode values
w.show();
//a.processEvents();
return a.exec();
}
void sendMsg(void)
{
// Send heartbeat signal to another device
}
void receiveMsg(void)
{
// Read messages from the other device and update the variables
// These two values change continuously
objectDetected = true;
distanceToObject = 5.4;
}
void decide(void)
{
// The core function of the program. Takes relatively long time
// Run a decision-making algorithm which makes decisions based on the values received from the other device.
// Update some variables according to the made decisions
currentMode = Auto;
// Execute functions according to the made decisions.
setMode(currentMode);
}
int main(void)
{
timerStart(updateGUI, 500);
timerStart(sendMsg, 1000);
timerStart(receiveMsg, 10);
timerStart(decide, 500);
}
如何正确更新变量值的主窗口?
答案 0 :(得分:7)
您的主题不会更新MainWindow
,但它会在每次迭代时创建一个全新的QApplication
和MainWindow
。您的线程应该卡在QApplication::exec
内,直到您退出应用程序(例如关闭窗口)。只有这样,你的线程循环才能进一步发展。
通常,在从主线程外部进行更新时必须非常小心,因为通常必须在主线程内执行GUI操作。
考虑使用QThread
,它已经有自己的事件循环,您可以使用它来通过相应的插槽通知/更新您的窗口。
如果没有关于您实际想要实现的目标的更多详细信息,则无法为您提供进一步的指导。我至少建议您在主线程中创建QApplication
和MainWindow
(例如main
)。那么这取决于你想要的更新&#39;。如果您需要处理某些数据,则可以在第二个线程中执行此操作,并使用信号槽将结果发送到MainWindow
实例。如果你需要绘制到窗口,那么这必须直接在主线程中完成,或者你可能会找到一种方法从你的线程中渲染到一个单独的缓冲区(即QImage
)然后发送这个缓冲到主线程以将其绘制到窗口中。
我试着勾勒出这样的事情。但请注意,这既不完整也不可编译,只是一个大纲。
首先,您拥有MainWindow
并添加signal
,通知所有观察者开始工作(稍后会变得清晰)。此外,您添加slots
,只要您的某个值发生更改,就会调用该slots
。那些MainWindow
在主线程中运行(并且是class MainWindow : public QMainWindow
{
Q_OBJECT
public:
// constructors and stuff
void startWorking()
{
emit startWorkers();
}
public slots:
void onModeChanged(Modes m)
{
// update your window with new mode
}
void onDistanceChanged(float distance)
{
// update your window with new distance
}
signals:
void startWorkers();
};
的成员),因此可以更新窗口但是需要:
Worker
接下来,您将构建一个class Worker : public QObject
{
Q_OBJECT
public:
// constructors and stuff
public slots:
void doWork()
{
while(!done)
{
// do stuff ...
Modes m = // change mode
emit modeModified(m);
// do stuff ...
float distance = // compute distance
emit distanceModified(distance);
// do stuff ...
}
}
signals:
void modeModified(Modes m);
void distanceModified(float distance);
};
类,其中包含了所有&#39;后台工作&#39;你喜欢这样做(基本上是你的主题在原始代码中做了什么):
Worker
注意,QObject
必须继承doWork
,并且您的public slot
方法必须是signal
。此外,您为MainWindow
要通知的每个值添加emit
。不需要实现它们,因为它是由Qt MOC(元对象编译器)生成的。只要其中一个值发生变化,只需signal
相应的int main(int argc, char* argv[])
{
QApplication app(argc, argv);
MainWindow window;
// create a worker object
Worker* worker = new Worker;
// connect signals and slots between worker and main window
QObject::connect(worker, &Worker::modeModified,
&window, &MainWindow::onModeChanged);
QObject::connect(worker, &Worker::distanceModified,
&window, &MainWindow::onDistanceChanged);
QObject::connect(&window, &MainWindow::startWorkers,
worker, &Worker::doWork);
// create a new thread
QThread* thread = new QThread;
// send worker to work inside this new thread
worker->moveToThread(thread);
thread->start();
// show window and start doing work
window.show();
window.startWorking();
// start main loop
int result = app.exec();
// join worker thread and perform cleanup
return result;
}
并传递新值。
最后,你把所有东西放在一起:
QApplication
好的,让我们来看看吧。首先,在主线程中创建MainWindow
和Worker
。接下来,创建connect
对象的实例(可以在此处创建多个)。然后worker
将window
的信号发送到emit
的广告位,反之亦然。建立这些连接后,无论何时worker
发出信号,Qt都会调用连接的槽(并传输传递的值)。请注意,此连接跨线程边界。每当从与接收对象的线程不同的线程发出信号时,Qt将发送消息,该消息在接收对象的线程中处理。
然后你告诉Qt你希望你的QObject::moveToThread
使用QThread
住在另一个线程中。有关如何正确使用show
及其中的对象的详细说明,请参阅here。
其余的很简单。 window
startWorking
并开始处理。这里有不同的方法。我只是在这里调用emit
方法,然后startWorkers
worker
信号,它连接到doWork
的{{1}}方法,这样,doWork
将在另一个线程接收到该信号后开始执行。
然后调用运行主线程事件循环的QApplication::exec
,其中所有这些信号都由Qt处理。关闭应用程序后(例如,通过调用quit
或关闭主窗口),exec
方法将返回,您将返回main
。请注意,您需要正确关闭线程(例如,通过发送阻止while
循环的附加信号)并加入它。您还应删除所有已分配的对象(worker
,thread
)。为了简化代码示例,我在此省略了这一点。
回答你的问题
我有很多函数,例如updateClips和mavReceive,它们应该定期调用并相互独立运行。我应该为每个函数创建一个不同的Worker类,因为每个函数都有不同的信号,并且每个函数都有一个QThread对象,对吧?我不再需要startTimer()了吗?如果是,我如何控制每个函数的调用间隔(以前在startTimer()
中完成
来自评论:
答案很大程度上取决于你究竟是什么意思&#34;应该定期调用&#34;。谁应该打电话给他们?用户?或者他们应该定期执行?
原则上,您可以在一个线程中拥有多个工作线程。但是,如果它们应该一直工作(在while循环中旋转)它没有意义,因为一个正在运行而所有其他都被阻止。在这种情况下,每个工作人员都有一个线程。
如果我理解正确,您有兴趣定期更新某些内容(例如每500毫秒)。在这种情况下,我强烈建议使用QTimer
。您可以设置间隔然后启动它。然后,计时器将定期emit
timeout
信号,您可以连接到您想要执行的任何功能(更准确地说是slot
)。
Worker
的更新版本可能如下所示:
class Worker : public QObject
{
Q_OBJECT
public:
Worker()
{
QObject::connect(&modeTimer_, &QTimer::timeout,
this, &Worker::onModeTimerTimeout);
QObject::connect(&distanceTimer_, &QTimer::timeout,
this, &Worker::onDistanceTimerTimeout);
modeTimer_.start(500); // emit timeout() every 500ms
distanceTimer_.start(100); // emit timeout() every 100ms
}
public slots:
void onModeTimerTimeout()
{
// recompute mode
Modes m = // ...
emit modeModified(m);
}
void onDistanceTimerTimeout()
{
// recompute distance
float distance = // ...
emit distanceModified(distance);
}
signals:
void modeModified(Modes m);
void distanceModified(float distance);
private:
QTimer modeTimer_;
QTimer distanceTimer_;
};
注意,在构造函数中建立的连接。每当其中一个计时器超时,就会调用连接的slot
。然后,此插槽可以计算它需要的任何内容,然后使用与之前相同的MainWindow
将结果发送回主线程中的signal
。
因此,如您所见,您可以在一个Worker
(因此,一个线程)内拥有多个定时器/重新计算/更新信号。但是,实现的关键点是计算需要多长时间。如果它们花费很长时间(例如几乎与间隔一样长),那么您应该考虑使用多个线程来加速计算(意味着:在每个线程中执行一次计算)。当我慢慢地想要更清楚地了解您想要实现的目标时,我想知道是否只是关于这些定期更新您是否被滥用&#39;你问题中的主题。如果情况确实如此,那么您根本不需要该线程和Worker
。然后,只需将计时器添加到MainWindow
,然后将timeout
信号直接连接到slot
的{{1}}。