I am building a set of futures that work in a GUI thread during event dispatching and want to adopt the API of std::future but have hit an issue with chaining futures (non-blocking asynchronous execution).
Say we have a function that returns a future, and we want to execute something once the future is ready
disconnect().then([](std::future<State> &&f) {
...
});
It is my understanding that when doing nothing with the returned future from "then", it will be destroyed and the future will be aborted and the function will not anymore be executed. Therefore to make sure the chain is still present and executed properly, we would have to save the future somewhere (perhaps as a data member).
What should we do if we aren't interested in the returned future, but only in the chain of operations and that the function is execute properly? Should we move the returned future into a new std::future<R>(...)
and delete it in the lambda after it finished, such as "delete &f;" ? That looks wrong, though.
I don't believe that you need to hold the future
to ensure your completion handler is called. Destroying the second future
doesn't require that the shared_state is destroyed, it just decreases its reference count.
One way of implementing then
by yourself, from Herb Sutter's C++ Concurrency presentation in 2012, is as a free function that calls std::async
(page 6 of the slides PDF):
template<typename Fut, typename Work>
auto then( Fut f, Work w ) -> future<decltype(w(f.get()))>
{ return async([=]{ w( f.get() ); }); }
This occupies a thread, but you get the semantics that the caller of then( disconnect(), [](std::future<State> &&f) {...});
doesn't need to block. And even if we throw away the result of that then
call, the returned future
is destroyed, and its shared state cleaned up, but future f
that you passed in is not destroyed.
Then there's the rule that destroying a future
returned by async
may block until the async
completes, which means that if you throw away the result of this then
implementation, you may block anyway (page 21). So you can't do that after all.
In the desired effect, then
is going to operate as if it put a reference to your function object into a 'continuation slot' of the shared state of the future
, along with a second promise
of the return type of your function object, and returns the future
of that second promise
. So the original future
's shared state is referenced by the original promise
, and the second future
's shared state is referenced by the second promise
, so neither shared state will be destroyed by whatever you do with the second future
.
Once the original promise
is fulfilled, the shared state sees it has the continuation slot occupied, so does a make_ready_future
out of the value just fulfilled and gives it to your function object, setting the value into the second promise
, which has no effect because you threw the second future
away. But the "no effect" is on the output of your lambda.
(Implementation-wise, the second future
may well be the only reference to the second shared state, the promise
in there is just an illustration of behaviour, that the second shared state can be destroyed without forcing the first shared state to be destroyed. And I expect internally it can do better than make_ready_future
on the value...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With