If you're building .NET apps, async-first isn't optional anymore. Ten years later, the application patterns that maximise its value have been established through production experience.
Why async matters for throughput
ASP.NET Core's threading model is based on the thread pool. When a request thread blocks waiting for I/O (database query, HTTP call, file read), that thread cannot serve other requests. In a synchronous application, each concurrent request requires a thread. In an async application, one thread can handle many concurrent I/O-bound requests because it releases back to the thread pool while waiting. Under high concurrency with I/O-bound workloads, async ASP.NET Core can handle 5-10x more concurrent requests with the same number of threads.
The async all the way principle
Async works best when it is consistent throughout the call chain. A single synchronous blocking call in an otherwise async chain degrades throughput to synchronous levels for that operation. The common violation is calling .Result or .Wait() on a Task in a code path that is otherwise async. This can also cause deadlocks in synchronisation-context environments. The rule is: if the callee is async, the caller should be async.
ValueTask vs Task
Task
Channel for producer-consumer patterns
System.Threading.Channels.Channel