Попробовал сделать вспомогательный класс, который бы содержал внутри себя экземпляр asio::io_service и пул рабочих нитей, на которых бы дергались методы asio::io_service::run. Как водится, документацию внимательно прочитал только после того, как обнаружил, что ничего не работает. Ну а когда прочитал, был несколько удивлен.
Оказывается io_service::run возвращает управление сразу же, если сейчас в io_service нет никаких задач. Т.е., если нет ни таймеров, ни отслеживаемых событий ввода-вывода, то io_service::run сразу же завершает свою работу и возвращает управление.
С одной стороны, такое поведение можно понять. Если приложение полностью базируется на asio, то в нем всегда будет что-то в очереди задач io_service. А когда не будет, то значит приложение завершило все свои действия и можно прекращать работу.
Но вот в моем случае оказалось не так. У меня необходимость выполнения операций ввода-вывода возникает время от времени. Когда возникает, я выдаю задачи io_service, когда не возникает, io_service ничего не делает.
Первоначальная задумка была в том, чтобы создать пул рабочих потоков для asio. Каждый поток дернул бы io_service::run и заснул бы, если у asio нет задач. Задачи появились -- потоки проснулись, отработали свое и заснули бы вновь. Перед завершением работы я бы дернул io_service::stop, рабочие потоки бы проснулись, на них бы завершились вызовы io_service::run и все остались бы довольны.
Но нет, так просто не получилось. Когда задач для asio нет, рабочие потоки сразу же вылетают из io_service::run и сразу же завершают свою работу. Чтобы они не завершали свою работу сразу же, нужно либо делать в рабочих потоках цикл, в котором постоянно дергается io_service::run, либо придумывать что-то еще.
В итоге сделал схему, в которой создается один лишний таймер, срабатывающий раз в час. Срабатывающий только для того, чтобы перезадать себя самого еще на час. А потом еще на час и т.д.
Наличие этого таймера создает для io_service видимость наличия работы, которая еще не выполнена. Что и не позволяет вызовам io_service::run на рабочих потоках завершаться сразу же. Завершение происходит только тогда, когда в конце работы всего приложения явным образом вызывается io_service::stop.
В общем, был удивлен поведению io_service::run. Такое впечатление, что ее задизайнили под такие сценарии использования, когда у приложения нет никаких других задач, кроме ввода-вывода. Еще больше я удивился тому, что io_service::stopped возвращает true не только в том случае, когда io_service::stop вызывали явно. Но и в том случае, когда io_service::stop вообще не вызывали, а у io_service просто не оказалось никакой работы.
Остается надеяться, что это просто я из-за недостатка опыта работы с asio что-то не так понял, и что пул рабочих потоков для io_service::run можно организовать более прямым образом, без обходных маневров с липовыми таймерами.