https://www.boost.org/doc/libs/1_75_0/doc/html/thread/synchronization.html
The returned futures behave as the ones returned from boost::async, the destructor of the future object returned from then will block. This could be subject to change in future versions.
(2015) https://www.boost.org/doc/libs/1_75_0/doc/html/thread/changes.html
Version 4.5.0 - boost 1.58
Fixed Bugs:
#10968 The futures returned by async() and future::then() are not blocking.
(2017) https://github.com/boostorg/thread/issues/194
I'm aware of the issue. Boost.thread did it by design following std::async and the first proposals of std::future::then.
The problem is that changing the behavior (even if it is not desired in some contexts) would be a breaking change. I would need to crate a new version, but Idon't want to create a new version using compiler flags as this has become a nightmare to maintain.
So I will need to create a boost/thread_v5 which will produce different binaries lib_boost_thread_v5.a. Boost.Threads is not structured this way, and this will mean a lot of work.
This is the single way to avoid breaking changes, but before doing that I would like to enumerate all the breaking changes that should be on this new version.
It seems the destructor of future object returned from async has really changed to non-blocking at some time point after 2017, so you should use .get() to block.
I can not make it block because:
BOOST_THREAD_FUTURE_BLOCKING(not documented) is not #defined in all cases;
- if you
#define BOOST_THREAD_FUTURE_BLOCKING manually, you can see that ~future_async_shared_state_base() will be called when the future is destroyed and the lambda passed to async has finished running (i.e. when the boost::shared_ptr's use_count drops from 2 to 1 to 0), so the destructor of future object returned from async (when the boost::shared_ptr's use_count drops from 2 to 1) will still not block.
https://www.boost.org/doc/libs/1_75_0/boost/thread/detail/config.hpp
#if BOOST_THREAD_VERSION>=5
//#define BOOST_THREAD_FUTURE_BLOCKING
#if ! defined BOOST_THREAD_PROVIDES_EXECUTORS \
&& ! defined BOOST_THREAD_DONT_PROVIDE_EXECUTORS
#define BOOST_THREAD_PROVIDES_EXECUTORS
#endif
#else
//#define BOOST_THREAD_FUTURE_BLOCKING
#define BOOST_THREAD_ASYNC_FUTURE_WAITS
#endif
https://www.boost.org/doc/libs/1_75_0/boost/thread/future.hpp
in async:
void init(BOOST_THREAD_FWD_REF(Fp) f)
{
#ifdef BOOST_THREAD_FUTURE_BLOCKING
this->thr_ = boost::thread(&future_async_shared_state::run, static_shared_from_this(this), boost::forward<Fp>(f));
#else
boost::thread(&future_async_shared_state::run, static_shared_from_this(this), boost::forward<Fp>(f)).detach();
#endif
}
when the future is destroyed and the lambda passed to async has finished running:
~future_async_shared_state_base()
{
#ifdef BOOST_THREAD_FUTURE_BLOCKING
join();
#elif defined BOOST_THREAD_ASYNC_FUTURE_WAITS
unique_lock<boost::mutex> lk(this->mutex);
this->waiters.wait(lk, boost::bind(&shared_state_base::is_done, boost::ref(*this)));
#endif
}
C++20 has moved to coroutine which is able to split control flow in more complex cases than future.then: CppCon 2015: Gor Nishanov “C++ Coroutines - a negative overhead abstraction". You can try libraries like cppcoro or boost.fiber and pause the task function inside complex control structures (e.g. co_await inside if inside while inside for).
C++23 Executor TS: in initial versions of A Unified Executors Proposal for C++ there were OneWayExecutor execute(fire-and-forget) TwoWayExecutor twoway_execute(return a future) then_execute(take a future and a function, return a future) etc but in recent versions they were replaced by sender and receiver.