2.3 对消息的反馈与异步收集

        本文是 《基于 Qt 实现消息总线》的其中一节,建议全章阅读。


        在消息总线中,我们使用 QtPromise 来实现异步的处理。选择 Promise 是因为它简单有效,先说一下 Promise 的基本规范。

Promise 规范

  • Promise 是一个拥有 then 方法的对象
  • Promise 的状态
        • 等待态( Pending
处于等待态时, promise 需满足以下条件:
可以迁移至执行态或拒绝态
        • 执行态( Fulfilled 实现态
处于执行态时, promise 需满足以下条件:
不能迁移至其他任何状态
必须拥有一个不可变的终值
        • 拒绝态( Rejected
处于拒绝态时, promise 需满足以下条件:
不能迁移至其他任何状态

                •必须拥有一个不可变的据

  • Then 方法

        • promise then 方法接受两个参数:
promise.then ( onFulfilled , onRejected )
参数可选, onFulfilled onRejected 都是可选参数。
如果 onFulfilled 不是函数,其必须被忽略
如果 onRejected 不是函数,其必须被忽略
        • onFulfilled 特性
如果 onFulfilled 是函数:
promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
promise 执行结束前其不可被调用
其调用次数不可超过一次
        • onRejected 特性
如果 onRejected 是函数:
promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
promise 被拒绝执行前其不可被调用
其调用次数不可超过一

        回调

        • 调用时机
onFulfilled onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用
        • 调用要求
onFulfilled onRejected 必须被作为函数调用(即没有 this 值)
        • 多次调用
同一个 promise then 方法可以被调用多次
promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次 回调

        返回

        • then 方法必须返回一个 promise 对象
promise2 = promise1.then( onFulfilled , onRejected );  
        • 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程: [[Resolve]](promise2, x)
        • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
        • 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
        • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据
  • Promise 解决过程
        • Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,表示为 [[Resolve]](promise, x)
        • 如果 x Promise ,则使 promise 接受 x 的状态
如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
如果 x 处于执行态,用相同的值执行 promise
如果 x 处于拒绝态,用相同的据因拒绝 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>,所以上面分两种情形分别实现。

        对消息的反馈与异步收集,就将这么多了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值