如何构造 "future inside future"

How to structure "future inside future"

我正在构建一个顶层与驱动层通信的系统,驱动层又与 I2C 层通信。我已将我的 I2C 驱动程序放在消息队列后面,以使其线程安全并序列化对 I2C 总线的访问。 为了 return 对驱动程序的回复,I2C 层 return 是一个 std::future 内部有一个字节缓冲区,当 I2C 总线读取实际发生时填充。

这一切都有效,我喜欢它。

我的问题是我还希望驱动程序 return 到顶层的未来,但是这个未来将取决于以前的未来(当 I2C 驱动程序 future-return 是字节缓冲区,驱动程序将不得不解释和调节这些字节以获得更高级别的答案),我在使这种依赖性“很好”方面遇到了问题。

比如我有一个PCT2075温度传感器芯片的驱动,我想要一个:

future<double> getTemperature()

那个驱动程序中的方法,但到目前为止我想不出比制作中间“未来持有人”更好的方法 class 然后 return 那:

class PCT2075
{
public:
    class TemperatureFuture
    {
    private:
        std::future<std::pair<std::vector<uint8_t>, bool>> temperatureData;

    public:
        TemperatureFuture(std::future<std::pair<std::vector<uint8_t>, bool>> f);

        template< class Clock, class Duration >
        std::future_status wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time) const;

        void wait() const;  // wait and wait_until just waits on the internal future
        double get();
    };

    TemperatureFuture getTemperature();
};

这个结构有效,我可以继续使用它,但出于某种原因我对它不是很满意(虽然我不能完全解释为什么......:/)。

所以我的问题是:

Ps。我还有另一种方法,其答案依赖于两次 I2C 读取,因此有两种不同的未来。可以将其重新设计为仅具有一对一的依赖关系,但当前的方法可以处理一对多的变体,因此如果潜在的新提案也可以的话,那就太好了。

只是重新解释以前的未来结果或收集多个未来的未来是 std::async(std::launch::deferred, ...) 的好工作。这不会启动任何线程,它会根据请求执行。

  std::future<int> f1 = std::async([]() -> int { return 1; });
  std::future<float> f2 = std::async(
     std::launch::deferred,
     [](std::future<int>&& f) -> float { return f.get(); },
     std::move(f1));
  std::printf("%f\n", f2.get());

缺点是某些功能将无法使用,例如wait_until.

相反,如果您需要在第一个未来准备就绪后启动一个新的异步操作(例如发送另一个 I2C 消息,或在线程池中计算更高级别的结果),C++ 不会提供任何更好的解决方案而不是将这部分作为您的原始任务。例如,您的 I2C 驱动程序可以接受 std::functions 列表作为回调。

您正在寻找一个名为 then 的操作,正如评论者所指出的那样,即使在 C++20 中也遗憾地丢失了该操作。

不过自己写athen也不难

template<typename Fun, typename... Ins>
std::invoke_result_t<Fun, Ins...> invoke_future(Fun fun, std::future<Ins>... futs) {
    return fun(futs.get()...);
}

template<typename Fun, typename... Ins>
std::future<std::invoke_result_t<Fun, Ins...>> then(Fun&& fun, std::future<Ins>... futs) {
    return std::async(std::launch::deferred, invoke_future<Fun, Ins...>, std::forward<Fun>(fun), std::move(futs)...);
}

我希望这样的事情没有标准化,因为一旦结果准备好,它就会对函数应该如何做大量假设运行。