从非Qt线程安全地调用Qt槽函数

3

我想调用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 ++在GUI线程之外显示图像(Boost thread)。也许,今天我会使用发布事件而不是QTimer... - Scheff's Cat
@Scheff 使用 QMetaObject::invokeMethod 的解决方案似乎是常见的模式,但与 QCoreApplication::postEvent 相比,它没有文档化的线程安全性。因此,我已经创建了一个错误报告,在这个问题得到解决之后,我将用那个解决方案来回答这个问题。https://bugreports.qt.io/browse/QTBUG-72599 - Superlokkus
2
QMetaObject::invokeMethod与排队连接一起实现是通过发布事件来实现的(请参见例如此处)。如果您查看代码,它与postEvent一样线程安全。我不完全确定为什么没有将其记录为这样; 可能是为了避免围绕线程安全性所涉及的混淆,特别是如果涉及直接连接(“目标”对象不受安全承诺的保护!) - peppe
1
https://codereview.qt-project.org/#/c/248378/ 添加了相关文档的部分内容。 - peppe
1个回答

3
原来,使用Qt::QueuedConnectionQMetaObject :: invokeMethod 实际上在其实现中使用了QCoreApplication :: postEvent (感谢 @peppe!)。但是,当满足以下条件时,不能保证其线程安全性:
  1. 使用Qt :: QueuedConnection
  2. 接收者的生命周期由Qt管理(或者至少在完成调用之后)
  3. 来自非主要qt线程对接收者没有其他操作
  4. 参数的生命周期由Qt管理(使用Q_ARS或按值调用时应该没问题)
这一点尚未记录在文档中。但我已创建了一个错误报告和一个qt论坛讨论,并且似乎已经打算这样做,因此已经创建了一张文档更改票
最终,我使用的是常见模式。
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
几点说明。1)invokeMethod始终是线程安全的。然而,安全性是关于 invokeMethod 本身内部逻辑的,而不是目标对象。例如,来自不同线程的对同一对象的直接调用将要求目标对象本身是线程安全的。 - peppe
1
  1. 目标的生命周期可以按您的意愿进行管理,不一定要使用 Qt 的设施。 重要的是,在调用 invokeMethod 期间目标仍然存在。(理论上,它可以立即删除。使用队列连接时,这可能意味着不调用目标方法。)
- peppe
1
  1. 不是真的 -- invokeMethod 本身是线程安全的!例如,您可以安全地在同一目标对象上从多个线程同时安排对给定函数的调用,而无需进行任何手动同步。 (然后,这些调用将从目标对象的关联线程中放置。)您甚至可以在同一时间从多个线程上对同一目标对象进行多个 直接 调用而不需要同步 -- 但是,这要求目标对象是线程安全的。
- peppe
1
  1. 当您放置一个排队的调用时,函数参数会在幕后发布的事件中被_复制_,因此当invokeMethod返回时,您可以安全地删除它们。 (这就是为什么您需要调用qRegisterMetaType来注册参数类型,而这又要求这些类型是可复制的。这里是循环,它将函数参数复制到事件对象中。)
- peppe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接