C# Programming Tutorial 0/45 lessons ~6 min read Lesson 38

    Async & Await

    async/await models asynchronous I/O without blocking threads — critical for ASP.NET Core scalability.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    async/await models asynchronous I/O without blocking threads — critical for ASP.NET Core scalability. async methods return Task or Task<T>; await suspends until completion, freeing the thread for other requests.

    Interviewers hammer ConfigureAwait, deadlock from .Result, ValueTask, and IAsyncEnumerable streaming. Never block on async code in web apps.

    The story

    A product catalog page loads names for twenty items from a REST API. Firing twenty sequential HTTP calls would take twenty round trips; Task.WhenAll runs them concurrently so the page renders as fast as the slowest single request — critical for ASP.NET Core APIs that must not block threads waiting on I/O.

    Understanding the topic

    Key concepts

    • async method returns Task/Task/void (avoid async void except handlers).
    • await captures context optionally; ConfigureAwait(false) in libraries.
    • Task.Run offloads CPU work to thread pool.
    • ValueTask reduces allocation for hot paths.
    • CancellationToken propagates cancel through async chain.
    • IAsyncEnumerable async streams with await foreach.
    text
    sequenceDiagram
    Caller->>AsyncMethod: await Task
    AsyncMethod->>ThreadPool: schedule continuation
    ThreadPool->>IO: network / disk
    IO-->>AsyncMethod: complete
    AsyncMethod-->>Caller: result

    Step-by-step explanation

    1. Compiler rewrites async method to state machine.
    2. await incomplete Task registers continuation.
    3. Thread returns to pool until I/O completes.
    4. SynchronizationContext resumes UI thread in desktop apps.
    5. Exception stored in Task; thrown on await.
    6. Parallel async: Task.WhenAll coordinates multiple.

    Practical code example

    Async API client with cancellation and WhenAll parallel fetch:

    csharp
    namespace TechLearningPro.AsyncAwait;
    public sealed class CatalogClient(HttpClient http)
    {
    public async Task<IReadOnlyList<string>> GetProductNamesAsync(
    IEnumerable<Guid> ids,
    CancellationToken ct)
    {
    var tasks = ids.Select(id =>
    http.GetStringAsync(
    quot;/api/products/{id}/name", ct));
    var names = await Task.WhenAll(tasks);
    return names.ToList();
    }
    public async IAsyncEnumerable<string> StreamNamesAsync(
    IEnumerable<Guid> ids,
    [EnumeratorCancellation] CancellationToken ct)
    {
    foreach (var id in ids)
    {
    ct.ThrowIfCancellationRequested();
    yield return await http.GetStringAsync(
    quot;/api/products/{id}/name", ct);
    }
    }
    }

    Line-by-line code explanation

    • CatalogClient(HttpClient http) receives a typed HTTP client from dependency injection.
    • GetProductNamesAsync(..., CancellationToken ct) propagates cancellation through the async chain.
    • ids.Select(id => http.GetStringAsync(...)) starts one async HTTP task per product ID.
    • await Task.WhenAll(tasks) waits for every parallel request to complete.
    • names.ToList() materializes the string array into a list for the return type.
    • IAsyncEnumerable<string> StreamNamesAsync streams names one at a time for large catalogs.
    • [EnumeratorCancellation] CancellationToken ct wires cancellation into async iterators.
    • ct.ThrowIfCancellationRequested() checks cancellation before each iteration.
    • yield return await http.GetStringAsync(...) emits each name as it arrives without buffering all.

    Key takeaway: WhenAll parallelizes I/O-bound calls. IAsyncEnumerable streams results without loading all at once. EnumeratorCancellation wires token.

    Real-world use

    Where you'll use this in production

    • ASP.NET Core controllers awaiting EF and HTTP.
    • Background services with PeriodicTimer.
    • File and cloud blob async uploads.
    • gRPC streaming responses.

    Best practices

    • Async all the way — no sync-over-async.
    • Pass CancellationToken through layers.
    • Use Task.WhenAll for independent I/O parallelism.
    • ConfigureAwait(false) in library code.
    • Prefer await using for async disposal.

    Common mistakes

    • .Result or .Wait() deadlocks with SynchronizationContext.
    • async void swallows exceptions in event handlers carelessly.
    • Task.Run in ASP.NET for normal request I/O — unnecessary.
    • Creating new HttpClient per request — use IHttpClientFactory.

    Advanced interview questions

    Q1BeginnerWhat does await do?
    Suspends method until Task completes; returns thread to pool.
    Q2BeginnerTask vs ValueTask?
    Task general purpose; ValueTask optimized when often synchronous complete.
    Q3IntermediateWhy ConfigureAwait(false)?
    Library avoids capturing UI/request context unnecessarily.
    Q4IntermediateTask.WhenAll vs Parallel.ForEach?
    WhenAll async I/O; Parallel.ForEach CPU-bound with threads.
    Q5AdvancedImplement timeout wrapper for any Task.
    Task.WhenAny(task, Task.Delay(timeout)); if delay wins throw TimeoutException; else return await task.

    Summary

    async/await enables scalable non-blocking I/O. Never block async with Wait/Result in ASP.NET. Propagate CancellationToken; use WhenAll for parallel I/O. IAsyncEnumerable streams async sequences. Next: dependency injection in ASP.NET Core.

    Ready to mark this lesson complete?Track your journey across the entire course.