You said:
I'm assuming a new dedicated thread outside of the async thread pool is required …
I would not jump to that conclusion. It depends entirely upon the nature of this “blocking task”.
If it simply is some slow, synchronous task (e.g. a CPU-intensive task), then you would stay within the Swift concurrency system and perform this synchronous task within a detached task or an actor.
If it is some blocking API that will wait/sleep on that thread, then, as Itai suggested, we would wrap it in a continuation, ideally, replacing that blocking API with a non-blocking one. If it is not practical to replace the blocking API with an asynchronous rendition, then, yes, you could spin up your own thread for that, effectively making it an asynchronous task, and then wrapping that within a continuation.
To be clear, if we are talking about some computationally intensive task, then you definitely do not want to use the continuation pattern. You want to remain within Swift concurrency’s cooperative thread pool.
The idea of the cooperative thread pool is that it constrains the number of threads to the number of CPU cores (except on the iOS simulator, which is artificially constrained even further). If you start spinning up other threads outside of the Swift concurrency system, it will no longer be able to reason correctly about the correct number of active threads.
When they talk about the Swift contract in which threads must be able to make “forward progress”, they are warning against ever sleeping a thread (or otherwise wait on a thread) because, again, the cooperative thread pool would be unable to reason about how many threads are actively running and/or whether a CPU core can switch to some other task.
For example, consider WWDC 2021 video, Swift concurrency: Behind the scenes, which discusses Preserving the runtime contract. They only single out primitives that require caution (e.g., locks) and unsafe primitives (e.g., semaphores). At no point do they say that synchronous tasks must be avoided. (Obviously, though, you would never run a slow, synchronous task on the main actor.)
So, if the question is merely how to integrate slow, synchronous code with your asynchronous code, I would advise against spinning up threads outside Swift concurrency. In this case, if the synchronous task is reasonably fast and off the main actor, I would simply intersperse it within the asynchronous routine. If it is slow enough to justify running it on a separate thread, I would just wrap it within a detached task or actor, and await that.
If, however, you are dealing with slow, synchronous API that effectively incorporates/requires one of those “unsafe primitives”, then, yes, you would first see if you could just replace that with an asynchronous rendition and wrap that within a continuation as Itai described. But that would be a worst-case scenario.