Async and await
Asynchronous programming with async and await is crucial for building responsive and scalable applications, particularly ones with I/O-bound operations like network requests or file system access. It prevents threads from being blocked while a long-running operation completes, which improves the overall throughput of the application.
The core idea
The primary goal is non-blocking operations. When an async method reaches an await on a task that is not yet complete, control goes back to the caller and the current thread is free to do other work. Once the awaited task finishes, the async method resumes, often on a thread pool thread (see the thread poolThe thread poolA set of reusable worker threads that take jobs from a queue.Opens in a new tab).
This is not parallelism. async/await mainly improves concurrency by making better use of existing threads, especially for I/O-bound work. It doesn’t make a single operation faster by running it on multiple threads, unless you combine it with constructs like Task.RunI/O-bound or CPU-bound?Async helps most when the work is waiting, and CPU-heavy work needs Task.Run.Opens in a new tab or Task.WhenAllTask combinatorsWait for all of several tasks, or just the first one to finish.Opens in a new tab.
The keywords
async: a modifier on a method declaration to indicate that the method is asynchronous. An async method can contain one or moreawaitexpressions. Withasync, the compiler can transform the method into a state machine to manage its asynchronous execution. Such methods typically returnTaskorTask<TResult>.await: used inside an async method to pause its execution until the awaitedTask(orTask<TResult>) completes. While paused, it does not block the calling thread. It returns control to the caller of the async method.
Task and Task<TResult>
Taskis an asynchronous operation that does not return a value. It signifies the future completion of some work.Task<TResult>is an asynchronous operation that returns a value of typeTResultwhen it completes.
What the compiler does with it
The C# compiler transforms an async method into a state machine (see how await works under the hoodHow await works under the hoodThe compiler turns an async method into a state machine, and the thread is freed while it waits.Opens in a new tab). When an await is reached and the awaited task has not completed, the method’s current state is saved, and control returns to the caller along with an incomplete Task. When the awaited task completes, a continuation is scheduled to resume the method from where it left off.
At least two Task objects are typically involved in an async method call: the task the async method returns (which the caller awaits), and the internal task or tasks the async method awaits. This distinction is key to understanding the flow and debugging asynchronous code.
Sources
- C# Async/Await Explained: Complete Guide with Examples, NDepend Blog · accessed May 16, 2025