Skip to content

Introduction to async and await Keywords

async and await are two C# keywords that let you write asynchronous code that looks like normal synchronous code. They were added in C# 5.0 (with .NET Framework 4.5, in 2012), and today they are used in almost every .NET application: ASP.NET Core, Entity Framework Core, Blazor, MAUI, WPF and more.

  • async is written on a method. It says: "this method contains asynchronous waiting".
  • await is written before a task. It says: "wait here for this task to finish, but don't block the thread while waiting".

Why were async and await added?

Asynchronous code existed in .NET before C# 5, but it was hard to write. You had to pass callbacks ("call this method when you are done"), and the code got split into many pieces. Look at the same logic written with ContinueWith (the old way) and with await:

// Old way: callbacks with ContinueWith
static Task ShowUserOldWay()
{
    return GetUserNameAsync(1).ContinueWith(task =>
    {
        string name = task.Result;
        return GetOrderCountAsync(name).ContinueWith(task2 =>
        {
            Console.WriteLine($"{name} has {task2.Result} orders");
        });
    }).Unwrap();
}

// New way: async and await
static async Task ShowUser()
{
    string name = await GetUserNameAsync(1);
    int orders = await GetOrderCountAsync(name);
    Console.WriteLine($"{name} has {orders} orders");
}

The second version reads from top to bottom, exactly like normal code, but it is still fully asynchronous. Try/catch, loops and if statements also work normally with await. That is the whole idea of these keywords.

Your first async and await program

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Fetching the user...");

        string user = await GetUserNameAsync(1);

        Console.WriteLine($"Hello, {user}!");
    }

    static async Task<string> GetUserNameAsync(int id)
    {
        await Task.Delay(1000);   // pretend this is a database call
        return "Rahul";
    }
}
Output
Fetching the user...
Hello, Rahul!

Let's read it line by line:

  1. static async Task Main(): Main is marked async so we can use await inside it. It returns Task (async Main is allowed from C# 7.1).
  2. async Task<string> GetUserNameAsync(int id): an async method that will give back a string in the future.
  3. await Task.Delay(1000): waits 1 second without blocking any thread.
  4. return "Rahul";: we return a plain string. The compiler wraps it in the Task<string> for us.
  5. string user = await GetUserNameAsync(1);: await takes the Task<string> and gives us the string inside it when it is ready.

Return types of an async method

Return typeUse it whenExample
TaskThe method returns nothing (like void)async Task SaveOrderAsync(Order order)
Task<T>The method returns a value of type Tasync Task<int> GetCountAsync()
voidOnly for event handlers (button clicks)async void btnSave_Click(object sender, EventArgs e)
ValueTask / ValueTask<T>Advanced: hot paths that usually finish without waiting (e.g. a cache hit)async ValueTask<Product> GetFromCacheAsync(int id)
IAsyncEnumerable<T>Returning many items one by one asynchronously (C# 8+)async IAsyncEnumerable<Order> StreamOrdersAsync()

Basic rules to remember

  • await can only be used inside a method marked async.
  • By convention, async method names end with Async: GetUserNameAsync, SaveChangesAsync.
  • Prefer returning Task instead of void, so the caller can await it and catch its errors.
  • Once you start using async, use it "all the way up": if a method awaits something, its caller should usually await it too.
  • Don't block on async code with .Result or .Wait(); use await.

A real ASP.NET Core example

This is how async and await look in a real Web API controller using Entity Framework Core:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly AppDbContext _context;

    public ProductsController(AppDbContext context)
    {
        _context = context;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        Product? product = await _context.Products.FindAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }
}

While SQL Server looks for the product, the request thread is returned to the thread pool and can handle other users' requests.

Summary

  • async marks a method that uses asynchronous waiting; await waits for a task without blocking.
  • They were added in C# 5.0 to replace messy callback code.
  • Async code written with them reads top to bottom like normal code.
  • Async methods return Task, Task<T>, ValueTask or (only for events) void.
  • Name async methods with the Async suffix and use async all the way.

In the next two lessons we look at what async really does and what await really does.