c++ 如何在Qt主线程中正确执行GUI操作?

f1tvaqid  于 2023-01-22  发布在  其他
关注(0)|答案(3)|浏览(191)

我有一个简单的程序,它由两个线程组成:
1.由Qt QApplication::exec操作的主GUI线程

  1. boost::asio::io_service操作的TCP网络线程
    TCP事件,例如连接或接收数据,导致GUI发生变化,最常见的是QLabel上的setText和隐藏各种widget,目前我正在TCP客户端线程中执行这些动作,这似乎很不安全。
    如何正确地将事件发布到Qt主线程?我正在寻找boost::asio::io_service::strand::post的Qt变体,它将事件发布到boost::asio::io_service事件循环。
jdzmm42g

jdzmm42g1#

如果不想让TCP类成为QObject,另一个选择是使用QMetaObject::invokeMethod()函数。
那么要求是你的目标类必须是一个QObject,并且你必须调用一个在目标上定义的slot。
假设您的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。

sy5wg1nm

sy5wg1nm2#

如果你的对象是从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-〉进程事件();在移除之前刷新队列;
  • 也使用dispatchToMainThread将删除排队;
up9lanfz

up9lanfz3#

对@CJCombrink优秀答案的推广不需要定义插槽,您也可以使用lambda:

QMetaObject::invokeMethod(
          myQObject, [=]() { /* ... whatever ... */ }, Qt::QueuedConnection);

相关问题