Share via

Async Await in C#

Pip 265 Reputation points
2026-02-26T18:57:22.52+00:00

Hi,

I'm using .NET 10 with VS 2026.

Can you please cover the history of this topic , Async Await in C#, along the C# versions?

Where can I used this topic ?

What is the best practice for this topic?

Thanks,

Developer technologies | C#
Developer technologies | C#

An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.

0 comments No comments
{count} votes

Answer accepted by question author
  1. Jack Dang (WICLOUD CORPORATION) 14,955 Reputation points Microsoft External Staff Moderator
    2026-02-27T06:57:19.3366667+00:00

    Hi @Pip ,

    Thanks for reaching out.

    First, about Task and ValueTask. Every time you return a normal Task, C# has to create a small object in memory to wrap the result. That’s usually fine, but if you have a part of your code that runs a lot (we call that a “hot path”), those allocations start to add up. That’s where ValueTask comes in. It’s kind of a lightweight version of Task. If the result is already ready or your method often completes synchronously, ValueTask lets you skip creating that extra object. So, you save some memory and CPU cycles. It’s worth noting that for most normal async code, Task is perfectly fine, ValueTask is mostly for when you really care about performance.

    Then there’s ConfigureAwait(false). By default, when an async method finishes, C# tries to continue on the same thread or context that started it. For example, if you’re on a UI thread, it goes back there. That’s usually what you want. But sometimes you don’t care about the context, like in a library or background work. In that case, using ConfigureAwait(false) tells C#: “Just continue anywhere, no need to go back to the original thread.” This can save time and also prevent some weird deadlocks that happen when code waits on the wrong thread.

    So, putting it together in practice:

    • If you’re in a performance-critical path and async calls often complete quickly or return cached results, consider ValueTask to reduce allocations.
    • In library or background code, use ConfigureAwait(false) to avoid unnecessary context switching and potential deadlocks.
    • For regular async calls that don’t happen hundreds of times per second, just stick with Task and normal await.

    Hope this clears it up a bit.

    If my answer was helpful - kindly follow the instructions here so others with the same problem can benefit as well.

    2 people found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Q&A Assist
    2026-02-26T18:57:44.6966667+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    Async/await in C# has a clear evolution and well-defined usage and best practices.

    History across C# versions

    • C# 5.0 (Visual Studio 2012)
      • This is where async/await was introduced as a “groundbreaking language concept” for asynchronous programming.
      • C# 5.0’s major feature list explicitly includes:
        • Asynchronous members (async/await model)
        • Caller info attributes
      • Asynchrony became a first-class language feature, changing how asynchronous code is written in .NET.
      • Reference: C# 5.0 is called out as the version where “the async and await model for asynchronous programming” was added.
    • Later C# versions
      • The core async/await model remains the same, but later versions added:
        • Async Main (ability for Main to return Task or Task<int> so await can be used directly in Main).
        • Asynchronous streams: await foreach to consume IAsyncEnumerable<T>.
        • Asynchronous disposables: await using with IAsyncDisposable.
      • These features extend where and how async/await can be used, but they build on the same model introduced in C# 5.0.

    Where async/await can be used

    Async/await is used anywhere asynchronous operations are needed without blocking threads:

    • General async methods
      • Any method, lambda, or anonymous method can be marked with async:
            public async Task<int> ExampleMethodAsync()
            {
                // ...
            }
        
      • Async methods typically:
        • Have async in the signature.
        • Return Task, Task<TResult>, void (for event handlers), or another awaitable type with GetAwaiter.
        • Contain at least one await expression.
    • UI and client apps
      • To keep UI responsive while doing I/O (HTTP calls, file I/O, database calls, etc.).
      • Example pattern:
            public async Task<int> GetUrlContentLengthAsync()
            {
                using var client = new HttpClient();
            
                Task<string> getStringTask =
                    client.GetStringAsync("https://learn.microsoft.com/dotnet");
            
                DoIndependentWork();
            
                string contents = await getStringTask;
            
                return contents.Length;
            }
        
    • Console apps and entry points
      • Main can be async by returning Task or Task<int>, which allows await directly in Main.
      • In earlier C# versions (before async Main), Task.Result or Task.Wait had to be used to block, which is now discouraged.
    • Asynchronous streams
      • Use await foreach to consume asynchronous sequences:
            await foreach (var item in asyncSequence)
            {
                // process item
            }
        
    • Asynchronous disposables
      • Use await using with types implementing IAsyncDisposable:
            await using var resource = await SomeFactoryAsync();
        

    Core behavior of async/await

    • Marking a method async enables two capabilities:
      1. The method can use await to create suspension points.
      2. The method itself can be awaited by its callers.
    • When await is used:
      • The async method runs synchronously until the first await.
      • At await, if the awaited task is not complete, the method is suspended and control returns to the caller.
      • When the task completes, execution resumes after the await.
      • Suspension at await does not count as exiting the method; finally blocks do not run at that point.

    Best practices for async/await

    • Prefer await over blocking
      • Avoid blocking on tasks with Task.Wait, Task.Result, or Task.WaitAll.
      • Use nonblocking patterns instead:
        • Retrieve result of a background task: use await instead of Task.Wait/Task.Result.
        • Continue when any task completes: use await Task.WhenAny instead of Task.WaitAny.
        • Continue when all tasks complete: use await Task.WhenAll instead of Task.WaitAll.
        • Delay without blocking a thread: use await Task.Delay instead of Thread.Sleep.
      • Blocking can cause deadlocks and blocked context threads and complicates error handling.
    • Write proper async method signatures
      • Use Task<TResult> when returning a value.
      • Use Task when no value is returned.
      • Use void only for event handlers.
      • By convention, name async methods with an Async suffix (for example, GetUrlContentLengthAsync).
    • Ensure async methods actually use await
      • An async method without any await runs synchronously; the compiler issues a warning.
      • This often indicates a design issue; either remove async or add proper awaits.
    • Use nonblocking patterns in calling code
      • If a program needs the result of a task, implement the await expression in a nonblocking manner.
      • This is especially important in UI and ASP.NET-style environments where blocking the context thread can deadlock.
    • Consider performance and ValueTask for hot paths
      • Returning Task always allocates a reference-type object.
      • In performance-critical paths where many async calls complete synchronously or return cached results, consider ValueTask (as discussed under “Consider using ValueTask type”) to reduce allocations.
    • Understand ConfigureAwait(false)
      • Task.ConfigureAwait(bool) controls how the continuation runs.
      • Incorrect use can cause performance issues or deadlocks; see the referenced ConfigureAwait FAQ for detailed guidance.
    • Write less-stateful async code
      • Avoid depending on global state or specific call ordering.
      • Prefer designs that depend on method return values rather than shared mutable state; this makes async code easier to reason about.

    Summary

    • History: async/await was introduced in C# 5.0 and expanded with async Main, async streams (await foreach), and async disposables (await using) in later versions.
    • Usage: use async/await for any I/O-bound or long-running operations where threads must not be blocked (UI apps, services, console apps, libraries).
    • Best practices: use await instead of blocking APIs, choose correct async return types, avoid async void except for events, consider ValueTask in hot paths, and follow nonblocking patterns like await Task.WhenAll/Task.WhenAny.

    References:


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.