Prevent io_context::run from returning

io_context::run runs until all scheduled tasks are completed. After that io_context::run will return and the caller thread will unblock:

boost::asio::io_context io_context;
// Schedule some tasks
io_context.run();
std::cout << "Job's done! Continue the execution\n";

However sometimes you may need to keep it running regardless if there are tasks to execute or not. Servers we've reviewed so far were always doing async_accept, so they always had at least one task scheduled, so we didn't really need to keep them running in such a way. However a client doesn't do async_accept and it's a normal thing for it not to have scheduled tasks at some point at all. To prevent io_context::run from returning you should use boost::asio::executor_work_guard (a former io_context::work which is currently deprecated) class instance. Its name is too long, so let's alias it right away:

using work_guard_type = boost::asio::executor_work_guard<boost::asio::io_context::executor_type>;

boost::asio::io_context io_context;
work_guard_type work_guard(io_context.get_executor());
// Schedule some tasks or not
io_context.run();
std::cout << "Sorry, we'll never reach this!\n";

You still need a way to stop your application somehow, and to stop it gracefully. You may use io_context::stop function:

boost::asio::io_context io_context;
work_guard_type work_guard(io_context.get_executor());
// Schedule some tasks or not
std::thread watchdog([&]
{
    std::this_thread::sleep_for(10s);
    io_context.stop(); // That's OK, io_context::stop is thread-safe
});
io_context.run();
std::cout << "We stopped after 10 seconds of running\n";

In that case io_context::run won't stop right away but do this in the nearest suitable point of time, and the rest of scheduled tasks will be discarded. And that may be exactly what you're needed.

You may also need to wait until all scheduled tasks are completed and return from io_context::run after that. To do so you just need to destroy io_context::work class instance. This operation is also thread-safe:

boost::asio::io_context io_context;
auto work_guard = std::make_unique<work_guard_type>(io_context.get_executor());
// Schedule some tasks or not
std::thread watchdog([&]
{
    std::this_thread::sleep_for(10s);
    work_guard.reset(); // Work guard is destroyed, io_context::run is free to return
});
io_context.run();
std::cout << "We stopped after 10+ seconds of running\n";

If you're going to call io_context::run once again after it returned, then you should call io_context::reset before that.

November 10, 2019

Learning plan

There are several new things we should learn before jumping into a bigger example of a server
A bigger example of a server where you'll need to apply everything you've learned so far
Principles you should take into consideration during the development of your applications
10. Prevent io_context::run from returning
How to keep io_context::run running even when there is no work to do
How execute a regular code within io_context::run polling loop
We've dealt with a single-threaded environment so far; now it's time to go multithreading
A special execution model with a custom load balancer