Runtime

.NET 11 runtime async: the runtime takes over await

In .NET 11, the runtime can suspend and resume async methods itself, so the C# compiler no longer generates a state machine for them. The feature is in preview and is opted into per project. The post shows the much smaller IL, cleaner stack traces, and what still changes when you debug.

Published
Reading time
7 min read
agent run #16
agent run #16
Go to original meziantou.net

Report this story

Is something wrong with the summary, or do you own this content? Your message comes straight to my inbox.

Type of message

Key points

  • Enable it per project with <Features>$(Features);runtime-async=on</Features>; it is still a preview in .NET 11.
  • The compiled method carries the MethodImplOptions.Async flag, and no state machine type, MoveNext or hoisted fields are emitted.
  • Await points become AsyncHelpers.Await calls, and the IsCompleted check keeps the fast path for completed awaits.
  • Live stack traces show each async method once, without AsyncMethodBuilderCore.Start frames.
  • Callers of a resumed method still do not appear on the stack, so use the parallel stacks window to follow the chain.
  • Breakpoints and stepping work on the method you wrote instead of a generated MoveNext.

Until now, the C# compiler rewrote each async method into a generated state machine that the runtime knew nothing about. In .NET 11, runtime async hands suspension to the runtime: the compiler marks the method with a new implementation flag and emits AsyncHelpers.Await calls, while the body stays as you wrote it with ordinary locals. In the author's sample, a state machine type and a 477-byte MoveNext collapse into one 82-byte IL method, and live stack traces list each async method only once.

The feature is in preview and is enabled per project through a Features property in the project file. Callers that already returned still do not reappear on the stack after a resume, so following await chains still needs tools such as the parallel stacks window. Breakpoints and stepping land on the code you wrote, and the .NET 11 runtime libraries already take this path even when your own code does not opt in.

Real-world use

  • Reading live stack traces from a profiler to debug a hanging async service without builder frames in the way.
  • Measuring on a separate branch whether a hot async path in an ASP.NET Core service changes in IL size or behavior under the preview runtime.
  • Stepping over await calls in an ASP.NET Core or Blazor bug hunt while inspecting ordinary local variables.

Why it matters

The compiled shape and debugging experience of your async code may change, so if you try .NET 11 you should verify performance and stack trace behavior in your own project.

This summary was generated by the agent from the original post on Gérald Barré (Meziantou's blog). The full text and all rights belong to its author and publisher. Go to original

Related stories

Feedback

Found a bug or have an idea? Your message lands straight in my inbox.

Type of message