与不是主线程的 GUI 线程通信

Posted

技术标签:

【中文标题】与不是主线程的 GUI 线程通信【英文标题】:Communicating with a GUI thread that is not the main thread 【发布时间】:2019-09-22 13:17:15 【问题描述】:

我设法在 worker C++ std::thread 中实现了一个基于 GUI Qt 的应用程序,如 here 所述。现在我需要主线程和工作线程进行通信。

我的问题是:如何将消息(浮点数数组)从主线程传递到工作线程,以便更新 GUI?

我有一个执行实时信号处理的应用程序。我的目标是创建一个可以插入到我的应用程序中的 Qt GUI,以在不影响实时方面的情况下可视化各种信号。我研究了如何实现这一目标的不同方法,并得出结论,这个post 非常详细地描述了我的需求并为它提供了解决方案。但是,没有关于主线程和工作线程如何相互通信的信息。

我尝试使用here 描述的 Futures/Promises 方法来完成线程间通信。虽然我能够运行这个示例,但我无法将它集成到我的项目中。原因是这种方法依赖于工作线程内部的繁忙循环,该循环不断检查主线程是否已发送新消息。但是,在 Qt 应用程序中,一旦进入a.exec() 中的主事件循环,程序就会阻塞。这可以防止繁忙的循环检查,从而导致程序死锁。

这就是我生成 GUI 工作线程的方式(基于 this 帖子)。

#include <thread>

// Start the Qt realtime plot demo in a worker thread
int argc = 0;
char **argv = NULL;
std::thread t1
        (
                [&] 
                    QApplication application(argc, argv);
                    MainWindow mainWindow;
                    mainWindow.show();
                    return application.exec();
                
        );

【问题讨论】:

通信需要如何进行?具有适当同步的共享内存应该足够了。我看不到任何与您在代码中描述的内容相关的内容。 我对通讯的工作方式没有要求。我没有研究过共享内存通信,但如果你认为这可以帮助我完成我需要的事情,我会研究它。所以让我们假设我启动了 GUI 工作线程(使用我发布的代码)。该线程将进入application.exec() 并且永远不会返回。如何使用共享内存方法将消息从我的主线程发送到工作线程,以便它使用消息更新 GUI?我是否需要信号/槽机制来通知 GUI 工作线程有新消息可用? 您应该能够通过使用Qt::QueuedConnection 连接类型调用QMetaObject::invokeMethod 来通知UI 线程。这基本上将一个事件发布到 Qt 的事件队列中。 @IgorTandetnik 如果在 UI 线程中定义了 mainWindow,我如何以 mainWindow 作为参数调用 QMetaObject::invokeMethod 谢谢伊戈尔。这绝对是解决问题的一种方法。看看@ypnos 的答案。它提供了一些关于为什么这可能是或可能不是最佳解决方案的见解。 【参考方案1】:

您可以使用 Qt 自己的方法在线程之间进行通信。每个 Qt 对象都有一个线程亲和性。如果您在单独的 GUI 线程中创建 MainWindow 对象,它将附加到该线程。如果您使用 Qt 信号和槽,并将它们与Qt::QueuedConnection 连接,则槽将在对象的线程中通过该线程的主循环被调用,而不管信号来自何处。

请注意,您可以在 MainWindow 类中定义信号,但可以从外部调用它们,因为它们是公共的。这意味着您不需要单独的发送对象。在MainWindow 构造函数(例如)中,您可以将其自己的信号连接到插槽或 lambda 方法。

为了能够发出MainWindow 信号,您可以在GUI 线程外将MainWindow 指针设置为nullptr,并将该指针(通过lambda 捕获)设置为您在GUI 线程内创建的新对象。在你的线程之外,只要你想发出一个信号,你就可以做某事。喜欢if (mainWindow) mainWindow-&gt;signal(…)。我认为您无论如何都需要检查,因为您的 GUI 是可选的。

两个音符:

    您也可以使用QMetaObject::invokeMethod 放弃信号,它删除了样板,但是可以通过引用在编译时检查的类方法来连接信号; QMetaObject 方法在运行时使用字符串匹配。同样使用公共信号但受保护的槽,您可以确保外部代码不会意外直接调用方法。 将信号连接到 lambda 时,请确保指定接收对象。 connect(this, &amp;MainWindow::signal, this, [this] …); 将在接收线程中执行 lambda,connect(this, &amp;MainWindow::signal, [this] …);不会

【讨论】:

非常感谢您花时间写出如此完整而准确的答案。尽管我是一个完整的 Qt 新手,但我很快就理解并实施了您提出的建议。 感谢您的反馈。很高兴听到这个消息! 请注意,您根本不允许在非主线程中触摸任何 GUI 类。 @peppe 用稍微更专业的术语来表达,绝大多数 Qt 都不是线程安全的(信号发射是少数例外之一)。此外,底层图形系统通常不是线程安全的(包括例如 X11)。这就是 Qt 期望您在单个线程中执行所有 GUI 操作的原因。 “Qt 的绝大多数”是夸大其词。 GUI 类不是线程安全的。其余大部分(例如核心类、网络等)都是可重入的。也可以在这里查看我的幻灯片kdab.com/wp-content/uploads/stories/…。【参考方案2】:

对于信号处理,您通常不希望 qts 虚拟调用的开销。但除此之外,

线程到gui通信的一个很好的解决方案是:git@github.com:midjji/convenient_multithreaded_qt_gui.git

那么它只是例如

enter code here
run_in_gui_thread(new RunEventImpl([]()
        QMainWindow* window=new QMainWindow();
        window->show();
    ));

可随时从任何线程调用,同时在后台为您进行设置。

【讨论】:

以上是关于与不是主线程的 GUI 线程通信的主要内容,如果未能解决你的问题,请参考以下文章

蓝牙通信 NSStream 是不是需要单独的线程?

主线程与子线程之间相互通信(HandlerThread)

[转]QT子线程与主线程的信号槽通信-亲测可用!

与命名管道和 WCF 服务的进程间通信:线程问题

多线程实现udp网络通信

Java多线程与并发库4.传统线程同步通信技术