In many languages the distinction is there because you have two versions of the IO routines, synchronous and asynchronous, and calling a synchronous one will block you asynchronous task. With virtual threads we took a different approach: if you make an IO call on a virtual thread it will do the IO asynchronously if at all possible and yield execution. This means you can take existing code and run it on virtual threads without modification and it will pretty much just work.
Not all IO operations can be done asynchronously on all platforms, and some new interfaces had to be introduced to allow for asynchronous name lookup, and some code needs refactoring because it had implicit assumptions that it would be used by tasks in a small thread pool and so used thread local values as a cache for expensive objects, but in general I think the design has achieved the goal of allowing existing code to be moved to virtual threads without the requirement to rewrite everything or scatter await / async all over the place.
Not all IO operations can be done asynchronously on all platforms, and some new interfaces had to be introduced to allow for asynchronous name lookup, and some code needs refactoring because it had implicit assumptions that it would be used by tasks in a small thread pool and so used thread local values as a cache for expensive objects, but in general I think the design has achieved the goal of allowing existing code to be moved to virtual threads without the requirement to rewrite everything or scatter await / async all over the place.