【问题标题】:How to properly execute GUI operations in Qt main thread?如何在 Qt 主线程中正确执行 GUI 操作?
【发布时间】:2015-12-07 14:12:30
【问题描述】:

我有一个由两个线程组成的简单程序:

  1. Qt QApplication::exec 操作的主 GUI 线程
  2. boost::asio::io_service操作的TCP网络线程

TCP 事件,例如连接或接收数据会导致 GUI 发生变化。大多数情况下,这些是 QLabel 上的 setText 并隐藏了各种小部件。目前,我正在 TCP 客户端线程中执行这些操作,这似乎很不安全。

如何正确地将事件发布到 Qt 主线程?我正在寻找boost::asio::io_service::strand::post 的Qt 变体,它将事件发布到boost::asio::io_service 事件循环。

【问题讨论】:

  • @MohamadElghawi 我知道信号和插槽。但是我到底应该怎么做呢?我不想在我的 TCP 客户端类中包含 Q_OBJECT,所以我不能将它连接到应用程序。

标签: c++ multithreading qt


【解决方案1】:

如果你不想让你的 TCP 类成为 QObject 另一个选项是使用QMetaObject::invokeMethod() 函数。

那么要求是您的目标类必须是一个 QObject,并且您必须调用在目标上定义的插槽。

假设你的 QObject 定义如下:

class MyQObject : public QObject {
    Q_OBJECT
public: 
    MyObject() : QObject(nullptr) {}
public slots:
    void mySlotName(const QString& message) { ... }
};

然后您可以从您的 TCP 类调用该插槽。

#include <QMetaObject>

void TCPClass::onSomeEvent() {
    MyQObject *myQObject = m_object;
    myMessage = QString("TCP event received.");
    QMetaObject::invokeMethod(myQObject
                               , "mySlotName"
                               , Qt::AutoConnection // Can also use any other except DirectConnection
                               , Q_ARG(QString, myMessage)); // And some more args if needed
}

如果您使用Qt::DirectConnection 进行调用,则插槽将在 TCP 线程中执行并且它可能/将会崩溃。

编辑:由于invokeMethod 函数是静态的,您可以从任何类调用它,并且该类不需要是 QObject。

【讨论】:

  • +1。我只想说目标方法本身不一定是插槽。只要通过元类型系统提供,也可以通过标记方法 Q_INVOKABLE 来实现。
  • 谢谢,这看起来很棒。您能否还请添加有关如何在"mySlotName" 中定义方法的信息?这是正常的Q_SLOT吗?
  • 是的,这是一个普通的插槽,我会尽快添加一些东西
  • @TomášZato 我在帖子中添加了更多代码以使其清楚。正如 MohamadElghawi 正确指出的那样,如果该方法标记为 Q_INVOKABLE,则它不需要像上面的示例代码所示是一个插槽。
  • @TheBadger 非常感谢,这对我帮助很大。
【解决方案2】:

如果您的对象继承自 QObject,只需发出一个信号并将其连接(使用标志 Qt::QueuedConnection)到主线程中的插槽。信号和插槽是线程安全的,应该优先使用。

如果它不是 QObject,那么您可以创建一个 lambda 函数(使用 GUI 代码)并使用单次 QTimer 在主线程中排队并在回调中执行它。这是我正在使用的代码:

#include <functional>

void dispatchToMainThread(std::function<void()> callback)
{
    // any thread
    QTimer* timer = new QTimer();
    timer->moveToThread(qApp->thread());
    timer->setSingleShot(true);
    QObject::connect(timer, &QTimer::timeout, [=]()
    {
        // main thread
        callback();
        timer->deleteLater();
    });
    QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
}

...
// in a thread...

dispatchToMainThread( [&, pos, rot]{
    setPos(pos);
    setRotation(rot);
});

原始感谢https://riptutorial.com/qt/example/21783/using-qtimer-to-run-code-on-main-thread

请小心,因为如果您删除对象,您的应用可能会崩溃。两个选项是:

  • 调用qApp->processEvents();在移除以刷新队列之前;
  • 也使用 dispatchToMainThread 对删除进行排队;

【讨论】:

  • 喜欢这个解决方案!
  • 这是一个很好的解决方案,令人惊讶的是它不是 Qt 库的一部分
猜你喜欢
  • 1970-01-01
  • 2014-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-21
相关资源
最近更新 更多