implementing future::then() equivalent for asynchronous execution in c++11
Solution 1
The problem with this approach to .then() is that you spawn 2 threads (that is costly) simultaneously, and second of them would block on its future.get/wait (if the first one would run long enough, of course) So, its better to use the work queue, to serialize the jobs execution order (and re-cycle the existing threads). Just look for a good Thread pool pattern implementation
Solution 2
In order to simplify the interface, I would "hide" the void
problem inside the implementation, similarly to what Herb did with his concurrent<T>
implementation. Instead of having 2 then
implementations, declare a helper function get_work_done
with 2 implementations:
template <typename T, typename Work>
auto get_work_done(future<T> &f, Work &w)-> decltype(w(f.get()))
{return w(f.get());}
template <typename Work>
auto get_work_done(future<void> &f, Work &w)-> decltype(w())
{f.wait(); return w();}
And then let template parameter detection take care of the rest:
template <typename T, typename Work>
auto then(future<T> f, Work w) -> future<decltype(w(f.get()))>
{
return async([](future<T> f, Work w)
{ return get_work_done(f,w); }, move(f), move(w));
}
Solution 3
no, it is not correct. if you pass return value of .get() to continuation, it will not be able to handle exception, propagated from .get(). you have to pass future to continuation, and call .get() manually, just like in boost.thread
Roman Kutlak
Updated on June 07, 2022Comments
-
Roman Kutlak almost 2 years
I have a few questions about the implementation of the function
then()
in Herb Sutter's talk. This function is used to chain asynchronous operations, the parameterf
is a future from one operation and the parameterw
is the 'work' for this operation (lambda).template <typename Fut, typename Work> auto then(Fut f, Work w) -> future<decltype(w(f.get()))> { return async([=]{ w(f.get()); }); }
An example of application would be:
std::future<int> f = std::async([]{ std::this_thread::sleep_for(std::chrono::microseconds(200)); return 10; }); auto f2 = then(std::move(f), [](int i){ return 2 * i; });
The main thread spawns the tasks but does not wait for any of them to finish.
Firstly,
future<T>
does not have a copy constructor. This means, that the suggested implementation can be only used withshared_future<T>
unless we change the call toasync()
to move the future into the lambda. This SO question suggested a way of doing it but it seems too complicated. I re-implemented the function and I am wondering whether my code is correct or whether I missed something...Secondly, the future that is passed to the
then()
function might bevoid
so we actually need 2 implementations ofthen()
, right? One for futures returningT
and one for futures returningvoid
.Lastly, should the lambda inside the body of
then()
not have a return statement so that we can actually return the value back? Without the return statement, then returnsfuture<void>
, right?I tried to address the above points and this is what I came up with. Is it correct?
template <typename T, typename Work> auto then(future<T> f, Work w) -> future<decltype(w(f.get()))> { return async([](future<T> f, Work w) { return w(f.get()); }, move(f), move(w)); } template <typename Work> auto then(future<void> f, Work w) -> future<decltype(w())> { return async([](future<void> f, Work w) { f.wait(); return w(); }, move(f), move(w)); }