C sharp - Asynchronous Programming in C# (async / await – In Depth)

Asynchronous programming allows a program to execute long-running operations without blocking the main thread.

It improves:

  • Responsiveness (UI apps)

  • Scalability (web apps)

  • Resource efficiency


1. Synchronous vs Asynchronous

Synchronous (Blocking)

public string GetData()
{
    Thread.Sleep(5000);
    return "Data Loaded";
}

Here:

  • The thread is blocked for 5 seconds.

  • Nothing else runs during this time.

Problem:

  • UI freezes

  • Web server thread is occupied


Asynchronous (Non-Blocking)

public async Task<string> GetDataAsync()
{
    await Task.Delay(5000);
    return "Data Loaded";
}

Here:

  • The thread is released while waiting.

  • Other work can execute.


2. Core Keywords

async

Marks a method as asynchronous.

await

Suspends execution until the awaited task completes.

Important:

  • await only works inside an async method.

  • It does NOT create a new thread automatically.

  • It enables non-blocking continuation.


3. Task and Task

C# uses Task to represent asynchronous operations.

Type Meaning
Task No return value
Task Returns value of type T

Example:

public async Task<int> AddAsync(int a, int b)
{
    await Task.Delay(1000);
    return a + b;
}

4. How async/await Actually Works

When the compiler sees:

await SomeTask();

It:

  1. Splits method into state machine

  2. Returns control to caller

  3. Resumes execution after task completes

This is called:
State Machine Transformation

The compiler generates hidden code to manage continuation.


5. CPU-bound vs I/O-bound Operations

I/O-bound (Best for async)

  • Database calls

  • API calls

  • File operations

  • Network requests

Example:

await httpClient.GetAsync(url);

CPU-bound (Use Task.Run)

If heavy computation:

await Task.Run(() => HeavyCalculation());

Why?
Because CPU work still blocks thread unless moved to background.


6. Avoiding Common Mistakes

Mistake 1: Using .Result or .Wait()

var result = GetDataAsync().Result;

This can cause:

  • Deadlocks

  • Thread blocking

Always use await.


Mistake 2: async void (Except Event Handlers)

Bad:

async void MyMethod()

Use:

async Task MyMethod()

Reason:

  • async void cannot be awaited

  • Exceptions cannot be caught properly

Exception:

  • Event handlers must be async void


7. Exception Handling in Async

try
{
    await GetDataAsync();
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

Important:

  • Exceptions are stored inside Task

  • They are thrown when awaited


8. Parallel Execution

To run tasks simultaneously:

Task t1 = Task.Delay(2000);
Task t2 = Task.Delay(3000);

await Task.WhenAll(t1, t2);

Task.WhenAll waits for all tasks.

Task.WhenAny waits for first completed.


9. Synchronization Context

In UI apps:

  • After await, execution resumes on original UI thread.

In ASP.NET Core:

  • No synchronization context.

  • Continuation runs on thread pool.

This affects performance and deadlock behavior.


10. Real-World Example (API Call)

public async Task<string> GetApiData()
{
    using HttpClient client = new HttpClient();
    var response = await client.GetStringAsync("https://example.com");
    return response;
}

Benefits:

  • Thread not blocked

  • Server handles more requests

  • UI remains responsive


11. Performance Benefits

Without async:

  • 1000 requests = 1000 blocked threads

With async:

  • Threads reused

  • Higher scalability

  • Lower memory usage

That is why ASP.NET Core heavily uses async.