QT插槽没有在主线程上被调用
QT slot not getting called on main thread
从我的 QT GUI 应用程序中插槽的线程上下文(按下按钮时),我试图启动一个工作线程以使用 CPU 密集的结果更新 GUI 的其他部分计算 - 这些结果将更新 table 或 google 之类的地图小部件 - 因此这需要发生在可以安全更新这些小部件的主 QT 应用程序线程中。
我遇到的问题是 updateGUIWidget
插槽永远不会被调用,除非我将连接类型更改为 Qt::DirectConnection
- 在这种情况下,它会在工作线程中被调用(这是不安全的)更新 GUI)。我检查了每个连接调用的结果,它们都很好,似乎某个地方的事件循环存在一些问题。我不确定是否需要将线程和工作对象分配为主窗口的成员,或者是否可以从插槽中的堆栈变量中分配。
void
mainwindow::on_importSimulatedFlight_clicked()
{
// experimental worker thread model adapted from YouTube tutorial
// by Giuseppe di Angelo https://www.youtube.com/watch?v=BgqT6SIeRn4
// auto thread = new QThread;
// note worker created in gui thread here - we will move its thread
// affinity to 'thread' below before starting it.
auto thread = new QThread;
auto worker = new Worker;
connect(thread, &QThread::started, worker, &Worker::doWork);
// connect(worker, &Worker::progressUpdate, this, &mainwindow::updateGUIWidget, Qt::DirectConnection);
connect(worker, &Worker::progressUpdate, this, &mainwindow::updateGUIWidget, Qt::QueuedConnection);
connect(worker, &Worker::workDone, thread, &QThread::quit);
connect(thread, &QThread::finished, worker, &Worker::deleteLater);
// move worker to separate thread
worker->moveToThread(thread);
thread->start();
}
主窗口有一个在mainwindow.h中声明的槽如下:
class mainwindow : public QMainWindow
{
Q_OBJECT
public:
explicit mainwindow(QWidget *parent = Q_NULLPTR);
~mainwindow();
...
public slots:
void on_importSimulatedFlight_clicked();
void updateGUIWidget(const param& rParam);
...
}
并在 mainwindow.cpp 中实现如下:
void
mainwindow::updateGUIWidget(const param& rParam)
{
... update widget components with rParam partial result here
}
我的工人如下:
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork() {
const QString result;
for (int i=0; i<5; i++) {
const MMRTimedRecord foo;
// QThread::sleep(1);
emit progressUpdate(foo);
}
emit workDone(result);
}
signals:
void progressUpdate(const MMRTimedRecord&);
void workDone(const QString &result);
};
它不起作用的原因是您的代码中存在严重缺陷:您试图发出对局部变量的引用,以便在不同线程的槽中处理。那是灾难的根源。
当您使用 Qt::QueuedConnection
时,您必须按值发出,如下所示:
void progressUpdate(MMRTimedRecord val);
也就是说你的MMRTimedRecord必须是可复制的,相应的,你的slot也必须是按值接受的。为什么信号 progressUpdate(const MMRTimedRecord&)
和插槽 updateGUIWidget(const param& rParam);
不匹配?
您可以查看此 以获得可能的解决方案。你可以做到
MainThreadEvent::post([&]()
{
// gui update stuff
}
);
在您的插槽中在主线程中执行 gui 更新,但可以肯定这是一种粗略的方法。尽管如此,我一直都在做这样的事情。小心悬挂指针和引用(使用 QPointer
)...,因为发出的事件独立于发出对象。或者,使用计时器方法。
这真的很简单——您不应该手动管理任何线程:
void Ui::slot() {
QtConcurrent::run([this]{
auto result = compute();
QMetaObject::invokeMethod(this, [this, r = std::move(result)]{
m_widget.setSomething(r);
});
});
}
您计算的数据类型应该是可移动的。
从我的 QT GUI 应用程序中插槽的线程上下文(按下按钮时),我试图启动一个工作线程以使用 CPU 密集的结果更新 GUI 的其他部分计算 - 这些结果将更新 table 或 google 之类的地图小部件 - 因此这需要发生在可以安全更新这些小部件的主 QT 应用程序线程中。
我遇到的问题是 updateGUIWidget
插槽永远不会被调用,除非我将连接类型更改为 Qt::DirectConnection
- 在这种情况下,它会在工作线程中被调用(这是不安全的)更新 GUI)。我检查了每个连接调用的结果,它们都很好,似乎某个地方的事件循环存在一些问题。我不确定是否需要将线程和工作对象分配为主窗口的成员,或者是否可以从插槽中的堆栈变量中分配。
void
mainwindow::on_importSimulatedFlight_clicked()
{
// experimental worker thread model adapted from YouTube tutorial
// by Giuseppe di Angelo https://www.youtube.com/watch?v=BgqT6SIeRn4
// auto thread = new QThread;
// note worker created in gui thread here - we will move its thread
// affinity to 'thread' below before starting it.
auto thread = new QThread;
auto worker = new Worker;
connect(thread, &QThread::started, worker, &Worker::doWork);
// connect(worker, &Worker::progressUpdate, this, &mainwindow::updateGUIWidget, Qt::DirectConnection);
connect(worker, &Worker::progressUpdate, this, &mainwindow::updateGUIWidget, Qt::QueuedConnection);
connect(worker, &Worker::workDone, thread, &QThread::quit);
connect(thread, &QThread::finished, worker, &Worker::deleteLater);
// move worker to separate thread
worker->moveToThread(thread);
thread->start();
}
主窗口有一个在mainwindow.h中声明的槽如下:
class mainwindow : public QMainWindow
{
Q_OBJECT
public:
explicit mainwindow(QWidget *parent = Q_NULLPTR);
~mainwindow();
...
public slots:
void on_importSimulatedFlight_clicked();
void updateGUIWidget(const param& rParam);
...
}
并在 mainwindow.cpp 中实现如下:
void
mainwindow::updateGUIWidget(const param& rParam)
{
... update widget components with rParam partial result here
}
我的工人如下:
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork() {
const QString result;
for (int i=0; i<5; i++) {
const MMRTimedRecord foo;
// QThread::sleep(1);
emit progressUpdate(foo);
}
emit workDone(result);
}
signals:
void progressUpdate(const MMRTimedRecord&);
void workDone(const QString &result);
};
它不起作用的原因是您的代码中存在严重缺陷:您试图发出对局部变量的引用,以便在不同线程的槽中处理。那是灾难的根源。
当您使用 Qt::QueuedConnection
时,您必须按值发出,如下所示:
void progressUpdate(MMRTimedRecord val);
也就是说你的MMRTimedRecord必须是可复制的,相应的,你的slot也必须是按值接受的。为什么信号 progressUpdate(const MMRTimedRecord&)
和插槽 updateGUIWidget(const param& rParam);
不匹配?
您可以查看此
MainThreadEvent::post([&]()
{
// gui update stuff
}
);
在您的插槽中在主线程中执行 gui 更新,但可以肯定这是一种粗略的方法。尽管如此,我一直都在做这样的事情。小心悬挂指针和引用(使用 QPointer
)...,因为发出的事件独立于发出对象。或者,使用计时器方法。
这真的很简单——您不应该手动管理任何线程:
void Ui::slot() {
QtConcurrent::run([this]{
auto result = compute();
QMetaObject::invokeMethod(this, [this, r = std::move(result)]{
m_widget.setSomething(r);
});
});
}
您计算的数据类型应该是可移动的。