从不同的 std::thread 更新 Qt GUI
Posted
技术标签:
【中文标题】从不同的 std::thread 更新 Qt GUI【英文标题】:Updating Qt GUI from a different std::thread 【发布时间】:2018-03-27 10:06:27 【问题描述】:在我希望摆脱 Qt 依赖的应用程序的一个单独组件中,我使用 std::thread
进行某些操作。我想在处理过程中对我的主应用程序进行更改。为此,我尝试将一个函数(即:function<void(UpdateNode*)> nodeUpdatedCallback
)从我的应用程序传递给我的组件。
此函数更新 UI,但由于我从另一个线程调用该函数,Qt 说我无法从非主线程访问 UI。
我看过很多文章通过使用QThread
和信号来解决这个问题,创建一个工作线程并将其移动到该线程。
既然我想使用std::thread
,是否可以使用std::thread
更新基于Qt的UI?
【问题讨论】:
你不能把函数传递给GUI线程并在那里执行吗? 问题是我想从后台线程调用函数(nodeUpdatedCallback),而它正在工作 这正是它的意思:你将一个函数传递给 GUI 线程并在那里执行。从哪个线程传递它并不重要 - 重要的是它传递到哪个线程到。 【参考方案1】:当我在多线程环境中使用 Qt 并且不想/不能使用惯用的信号/槽时,我将以下方法添加到我的对象中:
typedef std::function<void()> function_t;
void executeInObjectsThread(function_t const&);
private slots:
void executeInObjectsThreadSlot(function_t);
以及定义:
void MyObj::executeInObjectsThread(function_t const& f)
QMetaObject::invokeMethod(
this,
"executeInObjectsThreadSlot",
Qt::QueuedConnection,
Q_ARG(function_t,f)
);
然后在您的其他线程中调用
foo->executeInObjectsThread([=]
foo->addWidget(new QWidget(foo));
// ...
foo->editBox->setText(QString::number(currentResult));
foo->progressBar->setValue(n);
);
【讨论】:
请参阅this question了解此问题的详细处理方式。【参考方案2】:基本上,您永远不应该从另一个线程修改任何与 GUI 相关的东西。这有一个很好的理由:GUI 依赖于非线程安全的事件循环。您开始使用它,并且由于竞争条件,您将获得未定义的行为。感谢 Qt 保护您免于搞砸!
但是,您的问题的解决方案非常简单。在您的情况下,我所做的是定义线程修改的公共变量。所以我使用QTimer
来检查线程是否完成了它的工作。要知道线程是否已完成,您可以使用std::atomic<bool>
标志,该标志将在计算结束时设置(或使用std::promise/future
,我更喜欢)。然后,GUI 将只从主线程读取(也是线程安全的)结果并显示它们。这是 100% 安全的。
最近我用 Neblio 做了这个。 Check out my code there 用于在另一个线程中运行并在 GUI 上显示结果的自动更新程序。
【讨论】:
【参考方案3】:Qt 确实使用了信号/槽机制。您只提到了信号部分,但这里插槽也很重要。插槽存在于对象中,如果该对象是具有线程亲和性的 Qt 对象,则插槽将在该线程中执行。即使信号来自另一个线程也是如此。
当插槽所有者是QWindow
或与 UI 线程关联类似的东西时,这特别有用。它确保 UI 代码在正确的线程中运行。几乎可以肯定,UI 线程是您应用程序的 main()
线程。
所以您仍然可以从您的std::thread
收到信号。它不会导致插槽在相同的std::thread
中运行。
【讨论】:
以上是关于从不同的 std::thread 更新 Qt GUI的主要内容,如果未能解决你的问题,请参考以下文章