本文是 《基于 Qt 实现消息总线》的其中一节,建议全章阅读。
在消息总线中,我们使用 QtPromise 来实现异步的处理。选择 Promise 是因为它简单有效,先说一下 Promise 的基本规范。
Promise 规范
- Promise 是一个拥有 then 方法的对象
- Promise 的状态
•必须拥有一个不可变的据因
-
Then 方法
回调
返回
- Promise 解决过程
•如果 x 为普通对象,以 x 为参数执行 promise
Promise 实现
QtPromise 是第三方开源模块(Github 地址),为了大家在阅读分析 QtPromise 源码时更快上手,我们在这里简单分析一下其实现。
QtPromise 实现的几个要点是:
- PromiseData 内部共享状态
- 多个 QPromise 对象可以共享一个内部数据(状态),QPromise 只使用值传递,不需要引用形式
- 使用 QExplicitlySharedDataPointer 管理数据的生命期
- 共享数据记录 Promise 状态,也记录所有的回调函数
- PromiseResolver 管理解决过程
- 多个 PromiseResolver 对象可以共享一个内部数据(状态)
- 共享数据中保存 QPromise 副本
- PromiseValue 管理执行态
- PromiseError 管理拒绝态
- 通过 std::exception_ptr 保存异常
- 在需要时,通过 std::rethrow_exception 重新抛出异常
- PromiseDispatch 执行回调
- 通过 QEvent,在回调绑定的线程执行
- 获取返回值,捕捉回调中异常,继续向下一个 Promise 传递(通过 PromiseResolver)
另外,QPromise 的很多方法,其实是 then 方法的各种变化形态。
我们再分析一下与 Qt 相关的具体代码。
首先是共享状态以及生命期管理,要实现共享状态,就需要派生 QSharedData:
template <typename T, typename F>
class PromiseDataBase : public QSharedData
{
}
QSharedData 内部保存的引用计数,在引用计数变为 0 时,会自我销毁。Qt 的智能指针对象封装 QSharedData,增加、减少其引用计数。在 QPromise 中,使用了没有写时复制(COW)机制的 QExplicitlySharedDataPointer。
template <typename T>
class QPromiseBase
{
protected:
QExplicitlySharedDataPointer<QtPromisePrivate::PromiseData<T>> m_d;
};
其次是回调事件的分发,使用了 QCoreApplication::postEvent:
QObject* target = QAbstractEventDispatcher::instance(thread);
if (!target && QCoreApplication::closingDown()) {
return;
}
QCoreApplication::postEvent(target, new Event(std::forward<F>(f)));
传递事件给 QAbstractEventDispatcher,并不会得到处理,这里其实是利用了 post 事件的自动销毁机制,在析构方法中执行 Promise 回调。可以看一下 Event 的实现:
struct Event : public QEvent
{
Event(FType&& f) : QEvent(QEvent::None), m_f(std::move(f)) { }
Event(const FType& f) : QEvent(QEvent::None), m_f(f) { }
~Event() { m_f(); }
FType m_f;
};
QPromise 还有一个特殊的 wait 方法,阻塞等待异步完成,但是不阻止当前线程的事件分派,其实是在 wait 内部执行了事件分派循环:
template <typename T>
inline QPromise<T> QPromiseBase<T>::wait() const
{
while (m_d->isPending()) {
QCoreApplication::processEvents(QEventLoop::AllEvents);
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
return *this;
}
消息分派中的异步反馈
在消息总线的消息分派中,与 QPromise 一样,我们也使用 QSharedData 管理消息以及反馈收集的状态,这样消息分派到订阅者时,他们访问到的是一个共享的状态,哪怕在跨线程分派时被复制了。
template<typename T, typename R>
struct QMessageResultData : public QSharedData
{
T const msg;
mutable std::atomic<int> index{0};
QVector<PromiseResolver<R>> resolvers;
};
而在异步处理中,提前准备了与订阅者数量相同的 Promise,当这些 Promise 全部被解决时,发布者会得到通知,这里利用了 QtPromise::all 来收集所有异步反馈。实现如下:
QtPromise::QPromise<typename VectorResult<R>::type> await(int n) {
QVector<QtPromise::QPromise<R>> promises;
for (int i = 0; i < n; ++i) {
promises.append(QtPromise::QPromise<R>([this] (auto const & resolve, auto const & reject) {
resolvers.append({resolve, reject});
}));
}
return QtPromise::all(promises);
}
这里将每个 Promise 对应的 Resolver 都记录下来,后面由各个订阅者来解决这些 Promise,我们不关心订阅者的顺序,所以在返回的结果里面,无法知晓订阅者与结果的对应关系。
下面的代码展示了如何将消息处理的结果反馈给之前的 Promise:
template<typename R, typename T, typename F, typename RR = typename std::result_of<F(T &)>::type>
struct QMessageResultResolve {
static void invoke(PromiseResolver<R> const & rl, F const & f, T const & msg) {
rl.resolve(f(msg));
}
};
template<typename R, typename T, typename F>
struct QMessageResultResolve<R, T, F, QtPromise::QPromise<R>> {
static void invoke(PromiseResolver<R> const & rl, F f, T const & msg) {
f(msg).then([r = rl.resolve](R const & d) {
r(d);
}, rl.reject);
}
};
因为订阅者可能返回 R 或者 QPromise<R>,所以上面分两种情形分别实现。
对消息的反馈与异步收集,就将这么多了。