My advice, FWIW: spin up a utility thread and protect it from blocking operations, or re-engineer the process to avoid blocking the io_context threads.
For plain sockets, I realise the simplest implementation is just to do a manual "select" call using the native socket handle. Check the result of "available" first, and if 0, just construct a call to the normal socket select function (i.e go round the back of asio) with a timeout. It's a bit crude, and it's a shame that you have to, but it works. What doesn't work though is attempting to do the same for an ssl socket stream. There's no way to tell whether there is data available on the stream that can be read without blocking. You can perform a select on the raw socket OK, but you are in danger of blocking when there's unprocessed data in the SSL layer that's been decrypted and not returned, or read from the socket but not yet decrypted. So a solution if you aren't interested in SSL.