Not every await and new task produces an allocation in C#: ValueTask only allocates a state machine box if it yields asynchronously, plain task objects are pooled for common values and state machine box itself can also be pooled for frequently called methods (is opt-in), for example socket.SendAsync does not allocate thanks to this.
This will change further as "Runtime Handled Tasks" implementation comes along which will replace Roslyn-generated explicit state-machine code with runtime-provided suspension mechanism which will only yield/suspend the execution at true yield points, with further improvements to how the captured state that needs to persist across them is stored.
This will change further as "Runtime Handled Tasks" implementation comes along which will replace Roslyn-generated explicit state-machine code with runtime-provided suspension mechanism which will only yield/suspend the execution at true yield points, with further improvements to how the captured state that needs to persist across them is stored.