Asynchronous programming prevents thread blocking during I/O operations — essential for web servers, desktop apps, and mobile backends.

async and await

  async Task<string> FetchDataAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}

async Task Main()
{
    Console.WriteLine("Fetching...");
    string html = await FetchDataAsync("https://example.com");
    Console.WriteLine($"Received {html.Length} characters");
}

Main().Wait();
  

Methods returning Task or Task<T> can be marked async. The await keyword suspends execution until the task completes without blocking the thread.

Task.Run for CPU-Bound Work

  async Task<int> ComputeAsync()
{
    return await Task.Run(() =>
    {
        int sum = 0;
        for (int i = 0; i < 1_000_000; i++)
            sum += i;
        return sum;
    });
}
  

Offload CPU-intensive work to the thread pool; keep I/O on async methods directly.

Parallel Tasks

  async Task RunParallel()
{
    Task<string> t1 = FetchAsync("https://api.example.com/a");
    Task<string> t2 = FetchAsync("https://api.example.com/b");

    string[] results = await Task.WhenAll(t1, t2);
    Console.WriteLine($"Got {results.Length} responses");
}

async Task<string> FetchAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}
  

Cancellation Tokens

  async Task LongRunningAsync(CancellationToken token)
{
    for (int i = 0; i < 100; i++)
    {
        token.ThrowIfCancellationRequested();
        await Task.Delay(100, token);
        Console.WriteLine($"Step {i}");
    }
}

async Task Demo()
{
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
    try
    {
        await LongRunningAsync(cts.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Cancelled");
    }
}
  

Async Best Practices

  • Avoid async void except for event handlers.
  • Use ConfigureAwait(false) in library code.
  • Do not block on async code with .Result or .Wait() — it can cause deadlocks.

IAsyncEnumerable (C# 8+)

Stream async results:

  async IAsyncEnumerable<int> GenerateNumbersAsync()
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

await foreach (var n in GenerateNumbersAsync())
    Console.WriteLine(n);
  

ValueTask for Hot Paths

When async methods often complete synchronously, ValueTask avoids allocating a Task:

  public ValueTask<User?> GetCachedUserAsync(int id)
{
    if (_cache.TryGetValue(id, out var user))
        return ValueTask.FromResult(user);
    return new ValueTask<User?>(FetchFromDbAsync(id));
}
  

Exception Handling in Async Code

  try
{
    await FetchDataAsync(url);
}
catch (HttpRequestException ex)
{
    logger.LogError(ex, "HTTP request failed");
}
  

Exceptions thrown in async methods propagate to the awaiting caller.

Deadlock Warning

  // NEVER in ASP.NET or UI apps:
var result = FetchDataAsync(url).Result;  // can deadlock

// ALWAYS:
var result = await FetchDataAsync(url);
  

Async in ASP.NET Core Controllers

  [HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
    var user = await _repo.GetByIdAsync(id);
    return user is null ? NotFound() : Ok(user);
}
  

Action methods should return Task<IActionResult> for I/O-bound operations.