DEV Community

nikosst
nikosst

Posted on

Async/Await in C#: The Feature You Think You Understand (But Probably Use Wrong)

If you work with C# and .NET, chances are you use async and await every day.

Yet many developers:

  • don't fully understand what await actually does
  • accidentally introduce performance bottlenecks
  • block threads without realizing it
  • write async code that isn’t truly asynchronous

This article is written primarily for Senior Developers, but structured so Junior Developers can learn from it step by step.

The goal is simple:

Build a correct mental model of asynchronous programming.


The Biggest Misconception

One of the most common misconceptions in .NET development is the following:

async/await = new thread

This is not true.

Using async and await does not automatically create a new thread.

Instead, something much more efficient happens.

When an asynchronous operation starts:

  • the current thread initiates the operation
  • the thread returns to the thread pool
  • the application does not block while waiting

when the operation completes, execution continues later from the awaited point

This mechanism allows the runtime to reuse threads efficiently instead of keeping them idle while waiting for I/O operations such as:

  • database queries
  • HTTP requests
  • file access
  • external API calls

Because threads are not blocked, the server can handle many more concurrent requests with the same resources.

And this is exactly what makes modern .NET web APIs highly scalable.


The Real Problem Async Solves

Imagine this API endpoint:

public string GetData()
{
    var client = new HttpClient();
    var result = client.GetStringAsync("https://api.example.com").Result;
    return result;
}
Enter fullscreen mode Exit fullscreen mode

What happens here?

The thread:

  • waits for the network response
  • does nothing meanwhile
  • remains blocked

In a high-traffic server this leads to:

  • thread pool exhaustion
  • lower throughput
  • higher latency

The Correct Async Version

public async Task<string> GetDataAsync()
{
    var client = new HttpClient();
    return await client.GetStringAsync("https://api.example.com");
}
Enter fullscreen mode Exit fullscreen mode

Now the thread:

  • starts the request
  • returns to the pool
  • resumes when the response arrives

This is the key to scalability.


The Mistake Even Senior Developers Make

One of the most common mistakes developers make when working with asynchronous code is forcing it to run synchronously.

Example:

var result = GetDataAsync().Result;

or

GetDataAsync().Wait();

At first glance this might seem harmless. You call an async method and simply wait for the result.

But what actually happens is more problematic.

Even though GetDataAsync() is asynchronous, using .Result or .Wait() blocks the current thread while waiting for the operation to complete.

Instead of allowing the thread to return to the thread pool and continue other work, the thread just sits there doing nothing, waiting for the result.

This defeats the entire purpose of asynchronous programming.

Because of this blocking behavior, several problems can occur:

Deadlocks especially in environments with synchronization contexts (such as UI frameworks)

Thread starvation, threads remain blocked instead of serving other requests

Performance issues, the application handles fewer concurrent operations

Golden Rule

Async all the way down

Once a method becomes asynchronous, the entire call chain should remain asynchronous.

In practice, this means:

var result = await GetDataAsync();

instead of forcing the async code to behave synchronously.

Following this rule ensures that threads are not blocked and that your application can fully benefit from asynchronous execution.


Sequential vs Parallel Async

A key difference between mid-level and senior developers is parallel async execution.

Sequential

var user = await GetUserAsync();
var orders = await GetOrdersAsync();
var payments = await GetPaymentsAsync();
Enter fullscreen mode Exit fullscreen mode

Each operation waits for the previous one.


Parallel


var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var paymentsTask = GetPaymentsAsync();

await Task.WhenAll(userTask, ordersTask, paymentsTask);

Enter fullscreen mode Exit fullscreen mode

Now everything runs concurrently.

In microservices architectures this can reduce response time from 900ms to 300ms.


Another Common Mistake: async void

Bad:

public async void Save()
{
    await SaveToDatabase();
}
Enter fullscreen mode Exit fullscreen mode

Correct:

public async Task Save()
{
    await SaveToDatabase();
}
Enter fullscreen mode Exit fullscreen mode

async void means:

  • you can't await it
  • you can't catch exceptions
  • It should only be used for event handlers.

Cancellation Tokens (Production Must-Have)

Real production systems need cancellation support.

public async Task<string> DownloadAsync(
    string url,
    CancellationToken token)
{
    var client = new HttpClient();
    return await client.GetStringAsync(url, token);
}
Enter fullscreen mode Exit fullscreen mode

Why it matters:

  • aborted HTTP requests
  • timeouts
  • graceful shutdowns

A Tip That Separates Senior Developers

Don't write async code just because you can.

Use async when you have:

  • I/O operations
  • network calls
  • database queries
  • file access

For CPU-bound work, async often doesn't help.


A Simple Mental Model

Every time you write async code ask yourself:

  • Is this I/O bound?
  • Am I blocking a thread?
  • Can operations run in parallel?
  • Is cancellation supported?
  • Will this scale in production?

This is how senior engineers think.


Final Thoughts

async/await is one of the most important concepts in modern .NET development.

It is not just syntax.

It is an architectural tool.

Senior developers don't just write async code.

They design systems that:

  • leverage asynchronous execution
  • avoid concurrency pitfalls
  • scale under real production load

And that difference becomes obvious the moment your system starts receiving real traffic.


nikosstit@gmail.com

Top comments (0)