async things are (generally)
cooperatively executed/scheduled/etc. they choose when to give up control and allow other things to run (`await x`). this is in contrast to threads / processes, which are done
preemptively - the OS can say "stop doing that, do this", swap out the memory, and your thread basically can't do anything about it. (which is why there are thread priorities)
normal synchronous code never gives up control until it's done. so it'll block the cooperative/async scheduler until it finishes, whether it's maxing out the CPU or just waiting idly for a network request to respond. if there's anything else happening on that scheduler, it has to wait, which can lead to rather terrible behavior.
and last but not least: you generally cannot move async code between threads. async tends to imply you want same-thread concurrency guarantees, to avoid the cost of cross-thread mutexes and whatnot. so while you could in theory just interrupt an async scheduler and move it to a new thread, in practice that's not usually safe, so it isn't done.
you could build a system that allows jumping between threads though - that's essentially what green threaded languages do under the hood (like Go). which gives you a middle-ground of "nice thread-like semantics" but "nice async performance", but neither quite as good as a pure system in sometimes-important edge cases.
---
all that said, yes, you can just dedicate a thread to a single scheduler, run a synchronous func on it, and it has practically no additional cost over using a normal thread. but you have to choose to do that, it won't happen by default.