Skip to content

CSharp AsyncAwait Basics

Joe Care edited this page Dec 22, 2025 · 1 revision

C# Async/Await: Basics of Asynchronous Programming

This lesson introduces the core principles of using async and await for asynchronous programming in C#. Asynchronous operations are crucial for responsive and scalable applications, especially for long-running operations like network calls or file I/O.

Related wiki pages:

  • General C# topics: CSharpBible.md
  • Libraries and tasks: CSharpBible-Libraries.md
  • Async in MVVM / GUI: CSharpBible-MVVM-Standard.md, Avalonia-Apps.md, GUI_Window.md

Official documentation:


1. What is async/await?

Asynchronous programming lets your program start an operation and continue doing other work while waiting for the result (e.g., I/O, network, timers).

  • async marks a method as asynchronous and allows the use of await inside it.
  • await asynchronously waits for a Task to complete without blocking the current thread.

Contrary to some misconceptions:

  • There is no magical "event" called AwaitableEvent you must manage yourself – the C# compiler and .NET runtime handle the state machine behind async/await.
  • Asynchronous methods usually return Task or Task<T> (or ValueTask/ValueTask<T>), which represent the ongoing operation.

2. Basic async/await example

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("Starting...");

        try
        {
            await ProcessDataAsync("example payload");
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Error: {ex.Message}");
        }

        Console.WriteLine("Finished. Press any key to exit...");
        Console.ReadKey();
    }

    static async Task ProcessDataAsync(string data)
    {
        Console.WriteLine($"Processing data: {data}");

        // Simulate an I/O-bound operation
        await Task.Delay(2000); // non-blocking 2 second delay

        Console.WriteLine("Processing complete.");
    }
}

Key points:

  • static async Task Main is supported in modern C# (.NET Core / .NET 5+).
  • await Task.Delay(2000) does not block the thread; it returns control until the delay completes.
  • Exceptions inside ProcessDataAsync propagate to Main and can be caught there.

3. Tasks and return types

Typical async method signatures:

  • Task – asynchronous method that does not produce a result
  • Task<T> – asynchronous method that returns a result of type T

Example with a result:

static async Task<int> CalculateLengthAsync(string value)
{
    await Task.Delay(500); // simulate work
    return value.Length;
}

static async Task DemoAsync()
{
    int length = await CalculateLengthAsync("Hello");
    Console.WriteLine($"Length: {length}");
}

You should usually return Task/Task<T> instead of blocking on them (.Result or .Wait()), especially in UI or ASP.NET code, to avoid deadlocks.

For more patterns around tasks and libraries, see CSharpBible-Libraries.md.


4. Typical use cases

  • HTTP calls and web APIs (HttpClient.GetAsync, etc.)
  • Reading/writing files (FileStream.ReadAsync, File.ReadAllTextAsync)
  • Database queries (e.g. DbCommand.ExecuteReaderAsync)
  • Timers and background work (Task.Delay, PeriodicTimer)

These are often combined with MVVM view models and commands (see CSharpBible-MVVM-Standard.md and CommunityToolkit.MVVM.md).


5. Good practices

  • Prefer async all the way: avoid mixing blocking and async (no .Result/.Wait() on tasks).
  • Use Task.Delay instead of Thread.Sleep in async code.
  • Handle exceptions with try/catch (see CSharp-Exceptions-TryCatch.md).
  • For library code, consider ConfigureAwait(false) where you do not need the original synchronization context.

6. Next steps

  • Read the official async/await docs linked above.
  • Try converting a synchronous I/O method to an async version.
  • Combine this with LINQ from CSharp-LINQ-Where-OrderBy.md for processing result collections asynchronously.

Clone this wiki locally