【问题标题】:Invoke Qt slot safe from non-qt thread从非 qt 线程中安全调用 Qt 插槽
【发布时间】:2019-05-17 02:23:20
【问题描述】:

我想调用MyWidget的槽

class MyWidget : public QWidget {
Q_OBJECT

public slots:
void onFooBar(const std::string&);/*std::string& could also be replaced
    by a QString for easier meta system handling*/
};

但是因为在我的情况下boost::asio 使用线程,我不想对 Qt 做任何事情,我想从与主线程不同的线程调用这个插槽,但我不想随机线程控制。 (当然是在我让boost::asio 运行的线程上)

我该怎么做? QCoreApplication::postEvent 似乎是一个不错的选择,但文档并没有指出如何创建必要的 QEvent 的好方法。 QMetaObject::invokeMethodQt::QueuedConnection 看起来也不错,但没有记录为线程安全。

那么如何安全地从非 qt 托管线程调用 qt 槽?

(虽然Boost asio with Qt的标题暗示这可能是重复的,但这个问题对我来说似乎完全不同,这个问题不一定与boost::asio有关)

【问题讨论】:

  • 我只记得我曾经做过类似的事情(不知何故):SO: Qt C++ Displaying images outside the GUI thread (Boost thread)。可能是,今天我会使用已发布的事件而不是 QTimer...
  • @Scheff 使用QMetaObject::invokeMethod 的解决方案似乎是常见模式,但与QCoreApplication::postEvent 相比,没有记录线程安全。为此,我创建了一个错误报告,解决此问题后,我将使用该解决方案自行回答。 bugreports.qt.io/browse/QTBUG-72599
  • QMetaObject::invokeMethod 与队列连接是在发布事件方面实现的(参见例如here)。如果您查看代码,它与postEvent 一样是线程安全的。我不完全确定为什么没有这样记录;可能是为了避免混淆线程安全指的是什么,尤其是。如果涉及直接连接(“目标”对象不在安全承诺范围内!)
  • codereview.qt-project.org/#/c/248378 添加文档的相关位。

标签: c++ qt qt4 signals-slots qt4.8


【解决方案1】:

原来QMetaObject::invokeMethodQt::QueuedConnection 实际上在其实现中使用QCoreApplication::postEvent(感谢@peppe!)。但是保证它是线程安全的,当

  1. Qt::QueuedConnection一起使用
  2. 由 Qt 管理的接收者的生命周期(或 AFAIK,至少在完成调用之后)
  3. 除此之外,来自非主 qt 线程的收件人没有其他操作
  4. Qt 管理的参数的生命周期(使用 Q_ARS 或按值调用时应该没问题)

尚未记录。但是我已经创建了一个bug reportqt forum 讨论,并且似乎是有意的,并且已经创建了一个documentation change ticket

我最后用的是通用模式

class MyWidget : public QWidget {
Q_OBJECT

public slots:
void onFooBar(QString);
};

void asio_handler(const std::string& string, MyWidget* my_widget) {
QMetaObject::invokeMethod(
                        my_widget, "onFooBar", Qt::QueuedConnection,
                        Q_ARG(QString, QString::fromStdString(string))
                        );
}

【讨论】:

  • 几点说明。 1) invokeMethod 始终是线程安全的。但是,安全性与invokeMethod 本身内部的逻辑有关,而不是与目标对象有关。例如,从不同线程对同一对象的直接调用将要求目标对象本身是线程安全的。
  • 2) 可以根据需要管理目标的生命周期,而不必使用 Qt 工具。重要的部分是在调用invokeMethod 期间目标是活动的。 (理论上,它可以在之后立即删除。对于排队连接,这可能意味着不调用目标方法。)
  • 3) 不正确——invokeMethod 本身是线程安全的!例如,您可以安全地从多个线程同时调度对同一目标对象上的给定函数的排队调用,而无需任何手动同步。 (然后调用将从目标对象的关联线程进行。)您甚至可以同时从多个线程对同一个目标对象进行多个直接调用,而无需同步——但同样,这需要然后目标对象是线程安全的。
  • 4) 当您进行排队调用时,函数参数会在后台发布的事件中复制,因此您可以在invokeMethod 返回时安全地删除它们。 (这就是为什么您需要调用 qRegisterMetaType 来获取参数类型,这反过来又要求这些类型是可复制的。Here's the loop 复制事件对象中的函数参数。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
相关资源
最近更新 更多