Home > front end >  How to structure "future inside future"
How to structure "future inside future"

Time:11-26

I am building a system where a top layer communicates with a driver layer, who in turn communicate with a I2C layer. I have put my I2C driver behind a message queue, in order to make it thread safe and serialize access to the I2C bus. In order to return the reply to the driver, the I2C layer returns a std::future with a byte buffer inside that is filled out when the I2C bus read actually happens.

All this works and I like it.

My problem is that I also want the driver to return a future to the top layer, however this future will then depend on the previous future (when the I2C driver future-returns a byte buffer, the driver will have to interpret and condition those bytes to get the higher-level answer), and I am having problems making this dependency "nice".

For example, I have a driver for a PCT2075 temperature sensor chip, and I would like to have a:

future<double> getTemperature()

method in that driver, but so far I can't think of a better way than to make an intermediate "future-holder" class and then return that:

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();
};

This structure works and I can go forward with it, but for some reason I am not super happy with it (though I can't quite explain why... :/ ).

So my questions are:

  • Is there some pattern that can make this better?
  • Would it make sense to let TemperatureFuture inherit directly from std::future (I have heard that "do not inherit from std classes" is a good rule)?
  • Or is this just how you do it, and I should stop worrying about nothing?

Ps. I also have another method whose answer relies on two I2C reads, and thus two different futures. It is possible to rework this to only have a one-on-one dependency, but the current way can handle the one-on-multiple variant so it would be nice if a potential new proposal also could.

CodePudding user response:

A future that just reinterprets the results of a previous future or gathers multiple futures is a good job for std::async(std::launch::deferred, ...). This doesn't launch any thread, it executes on request.

  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());

The downside is that certain features will not work, e.g. wait_until.

If, instead, you need to launch a new asynchronous action once the first future is ready (e.g. send another I2C message, or compute the higher-level result in a thread pool), C does not offer any better solution than making this part of your original task. For example your I2C driver could accept a list of std::functions as callbacks.

CodePudding user response:

You are looking for an operation called then, which as commenters note is sadly missing even in C 20.

However, it's not hard to write a then yourself.

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)...);
}

I expect something like this wasn't standardised because it makes loads of assumptions about how the function should be run once the result is ready.

  • Related