The entire green thread is paused at yield() and resumed when other [controlling] thread calls resume() on it. Event loop has to account which thread yielded on which event source and resume corresponding thread when event arrives. Program as a whole doesn’t stop, only sequential blocking paths are put on waiting list. If each incoming query starts a new thread, then all queries are run concurrently around event/io loop, implicitly switching on deep yield() that programmer never spells out loud. That’s how e.g. openresty (nginx+lua) handlers work.
Surely it's nice not having to worry about it, but how is that better over explicit await?
If a sub-sub-sub-function suddenly decides to yield, you might not even know that your whole path is waiting for that. Right? Perhaps you need baz() to run asap but that isn't clear without looking deep into bar().
One big advantage is that higher order code can work with both sync and async code. The simplest example would be a for each iterator but this also applies to anything else that calls a function or method that is passed in