如何构造 "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();
};
这个结构有效,我可以继续使用它,但出于某种原因我对它不是很满意(虽然我不能完全解释为什么......:/)。
所以我的问题是:
- 是否有一些模式可以使它变得更好?
- 让
TemperatureFuture
直接从std::future
继承是否有意义(我听说“不要从std classes继承”是一个很好的规则)?
- 或者这就是你的做法,我应该不再担心什么?
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)...);
}
我希望这样的事情没有标准化,因为一旦结果准备好,它就会对函数应该如何做大量假设运行。
我正在构建一个顶层与驱动层通信的系统,驱动层又与 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();
};
这个结构有效,我可以继续使用它,但出于某种原因我对它不是很满意(虽然我不能完全解释为什么......:/)。
所以我的问题是:
- 是否有一些模式可以使它变得更好?
- 让
TemperatureFuture
直接从std::future
继承是否有意义(我听说“不要从std classes继承”是一个很好的规则)? - 或者这就是你的做法,我应该不再担心什么?
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)...);
}
我希望这样的事情没有标准化,因为一旦结果准备好,它就会对函数应该如何做大量假设运行。