In Python Trio, you would have a thread pool for each type of computation (potentially a "pool" of 1 thread shared between all tasks if that works for your application). You would await trio.to_thread.run_sync(...) [1] (pass it a normal non-async function, and a token representing the thread pool). This is a pretty simple wrapper that takes care of queuing the work to the thread pool, waiting for it to complete and for a message to be passed back to the Trio thread.
It works for simple situations. It certainly preserves backpressure, although a slow CPU task can impact other users of the same thread pool.
[1] https://trio.readthedocs.io/en/stable/reference-core.html#tr...