Yes, new handlers can be posted while a join is pending. join will keep waiting until the execution context runs out of work. This could be indefinite.
In fact, it is often indefinite e.g. in the case of a network server. The execution context will typically only "exit" once the server stops listening and all the connected sessions are closed.
In other words, join() is a synchronizing/rendez-vous operation, not a stop().
The simplest example to show that/how this works:
Live On Coliru
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
using boost::asio::thread_pool;
// convenience
using namespace std::chrono_literals;
static auto now = std::chrono::steady_clock::now;
static auto start = now();
static inline void trace(auto const&... msg) {
std::cout << std::setw(5) << (now() - start) / 1ms << "ms - ";
(std::cout << ... << msg) << std::endl;
}
void do_loop(int const i, thread_pool::executor_type ex) {
std::this_thread::sleep_for(1s);
trace("work ", i);
if (i < 10)
post(ex, std::bind(do_loop, i + 1, ex));
}
int main() {
boost::asio::thread_pool io;
post(io, std::bind(do_loop, 0, io.get_executor()));
trace("Start join");
io.join();
trace("Complete join");
}
This prints e.g.
0ms - Start join
1001ms - work 0
2001ms - work 1
3001ms - work 2
4002ms - work 3
5002ms - work 4
6002ms - work 5
7002ms - work 6
8003ms - work 7
9003ms - work 8
10003ms - work 9
11004ms - work 10
11004ms - Complete join
CAVEAT
Note that if the first async work is posted from another thread you may need additional work to prevent the pool from joining prematurely.
Without any synchronization, you wouldn't be able to tell in what order things execute: whether the post happens-before the join.
You can use an executor_work_guard (see make_work_guard) in such cases.
The same caveat applies to hybrid scenarios, where some async call chains might terminate, but unrelated threads are still posting tasks without synchronization. The order in which these operations happen is undeterministic (OS/implementation defined) and it may happen to extend the life of the pool.
Again
- make sure of work to guarantee the task will still execute.
- keep a separate flag (e.g. an
std::atomic_bool) indicating that no more work is being accepted by the pool
Is this documented
Yes:
This function blocks until the threads in the pool have completed. If stop() is not called prior to join(), the join() call will wait until the pool has no more outstanding work.