connect跨进程 qt_编写 Qt 跨线程异步调用器

本文使用 Zhihu On VSCode 创作并发布
本文使用 CC BY-NC-SA 4.0 许可协议,转载请注明来源

一、设计背景

众所周知,Qt 的信号槽系统提供了线程安全的跨线程异步执行代码的机制(Qt::QueuedConnection)。

使用该机制,可以让槽函数代码在另一个线程执行,并且可以携带参数,用户代码无需加锁,只要发射信号即可。

但很多时候,我们仅仅只想单次异步执行一段代码。若是通过信号槽机制执行,则就不得不声明一个信号函数,连接信号槽,再发射信号,这样显然很繁琐。

幸好,Qt 本身也知道这种需求的存在,提供了 QTimer::singleShot() 函数,可以跨线程异步执行槽函数,甚至还可以延迟执行——然而该函数只能执行无参数槽函数,不能执行其它类型的回调(如 lambda)。

所以,最好能够有一个类似 QTimer::singleShot(),但又可以接收任意参数个数的任意函数子的 API。

更新:5.3的老代码写太久,思维定势了,刚查了下5.4的 singleShot 是支持 Functor 的……那这篇文章留作该机制的技术探讨吧……
更新2:Qt 5.4之后的 QTimer::singleShot 实现有坑,有一个 Qt 事件循环机制理论上不应该出现的问题,详见文末更新。

考虑到异步执行时对执行结果的访问,可以参考 std::async(),返回一个 future 对象。但不能直接使用 std::future——因为它的 getwait 会阻塞住线程,对于 Qt 而言就会阻塞事件循环。

即,我们还需要一个不会阻塞事件循环的等待机制。

综上所述,需求总结如下:

  1. 提供跨线程异步执行代码的能力,让回调函数在目标线程执行;
  2. 提供对任意函数子的异步执行接口,可以接受具备任意参数个数的任意函数子;
  3. 提供延迟执行功能,以满足 QTimer::singleShot() 的所有功能,便于替代前者;
  4. 提供 future 返回对象,用于处理返回值和等待同步,接口与 std::future 类似;
  5. 提供不阻塞 Qt 事件循环的等待机制,用于供 future 使用。

二、异步回调实现

跨线程异步回调的实现,可以参考 Qt 的元对象机制。

Qt 通过元对象系统进行异步执行时(信号槽、QTimer::singleShot()、QMetaMethod::invoke 等),本质上是将回调函数封装为 QMetaCallEvent 对象,再通过 QCoreApplication::postEvent() 投送至目标对象。目标对象会在所属线程的事件循环中触发 QObject::event() 事件处理函数,解析事件并执行回调函数。

然而 QMetaCallEvent 是非公开接口,Qt 不保证其接口的可用和稳定性,因此我们需要仿照此流程自行封装。

2.1 异步回调事件类

新建一个事件类,继承自 QEvent,并注册获取事件类型编号:

class AsyncInvokeEvent : public QEvent {
    
 public:
  static const int kEventType;
  std::function<QVariant(void)> Function;
  std::promise<QVariant>;
  std::shared_future<QVariant>;
};
const int AsyncInvokeEvent::kEventType = QEvent::registerEventType();
AsyncInvokeEvent::AsyncInvokeEvent() : QEvent(QEvent::Type(kEventType)) {}

将用户通过 API 传入的回调函数封装为 std::function<QVariant(void)> 对象,以擦除类型信息,便于封入事件类中。

考虑到需要获取返回值,此处使用 Qt 的万能动态类型 QVariant 存储返回类型,但代价是返回值必须注册至 Qt 元对象系统——也可将 future 实现为模板类型,但这会导致代码复杂度大幅增加,并且不得不将 cpp 中的大部分流程暴露至头文件。

2.2 异步事件过滤器

将异步回调事件发送至目标线程时,需要有一个重写了 QObject::event() 函数的对象接受该事件。我们可以考虑为每个 Qt 线程建立一个事件过滤器,使用一个全局的字典保存,在使用时通过线程指针查询该字典,若未检索到则新建之,即惰性初始化:

AsyncInvokerEventFilter* filter;
{
    
  // Find event filter for given thread
  static std::atomic_flag flag = ATOMIC_FLAG_INIT;
  static QHash<QThread*, AsyncInvokerEventFilter*> filters;
  while (flag.test_and_set(std::memory_order_seq_cst)) {
     // Spin-lock
  }
  auto it = filters.find(thread);
  if (it == filters.end()) {
    
    it = filters.insert(thread, new AsyncInvokerEventFilter{
    thread});
  }
  filter = *it;
  flag.clear(std::memory_order_release);
}

拿到事件过滤器后,即可向其投送事件:

auto event = new AsyncInvokeEvent;
event->Function = function;
event->future = event->promise.get_future();
QCoreApplication::postEvent(filter, event);
return ev
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值