Basically, with async/await you need to allocate on the heap frames for each level of the async/await call stack. You basically get a linked list of stack frames.
This is essentially isomorphic to segmented stacks that Go ditched in favor of resizable stacks, because segmented stacks kill performance if a tight loop just happens to cross a segment boundary.
Are there any benchmarks to back that affirmation?
There's a bunch of benchmarks that compare C# async/await and Goroutines (e.g. https://alexyakunin.medium.com/go-vs-c-part-1-goroutines-vs-... ). Go still wins there, but they all miss the very important case of nested async/awaits.
These microbenchmarks also focus on looking at Go channels versus other synchronization methods, which in my experience is not that relevant (Go channels just suck).
I guess I'll need to write a blog post.
Results: Go - 32ms, C# - 494ms. The difference also becomes bigger as you add more recursion levels.
Green threads are decoupled from synchronization.
What I do like to see in C# is something akin to goroutines from Go or actors in Elixir.
The mistake is that async/await is not equivalent to virtual threads: the former can do only a subset of what the latter can.
A task is equivalent to a goroutine.