Skip to content

Introduction to Asynchronous Programming

In the previous lesson we saw that synchronous code does one task at a time and waits for each task to finish. In this lesson we learn the better way to handle waiting: asynchronous programming.

Asynchronous programming means you start a long-running task and, instead of standing and waiting for it, you continue with other work. When the task finishes, you come back and use its result.

Think of a waiter in a restaurant. The waiter takes your order, gives it to the kitchen, and goes to serve other tables. The waiter does not stand in the kitchen until your food is cooked. When the food is ready, the waiter brings it to you.

The breakfast example, done asynchronously

A smart cook makes breakfast like this:

  1. Put water on the stove. Don't wait.
  2. Put bread in the toaster. Don't wait.
  3. Pour juice.
  4. When everything is done, breakfast is ready.

The water still needs 3 minutes and the toast still needs 2 minutes, but they happen at the same time. Total time is about 3 minutes instead of 6.

Asynchronous code in C#

Here is the same program from the previous lesson, rewritten with async and await. Don't worry about every keyword yet; we will learn each one in detail later in this series.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Stopwatch watch = Stopwatch.StartNew();

        // Start all three tasks. None of these lines waits.
        Task waterTask = BoilWaterAsync();
        Task toastTask = MakeToastAsync();
        Task juiceTask = PourJuiceAsync();

        // Now wait until all three are finished.
        await Task.WhenAll(waterTask, toastTask, juiceTask);

        Console.WriteLine($"Breakfast ready in {watch.Elapsed.TotalSeconds:F0} seconds");
    }

    static async Task BoilWaterAsync()
    {
        Console.WriteLine("Boiling water started...");
        await Task.Delay(3000);   // wait 3 seconds WITHOUT blocking the thread
        Console.WriteLine("Water is boiled");
    }

    static async Task MakeToastAsync()
    {
        Console.WriteLine("Toasting bread started...");
        await Task.Delay(2000);
        Console.WriteLine("Toast is ready");
    }

    static async Task PourJuiceAsync()
    {
        Console.WriteLine("Pouring juice...");
        await Task.Delay(1000);
        Console.WriteLine("Juice is ready");
    }
}
Output
Boiling water started...
Toasting bread started...
Pouring juice...
Juice is ready
Toast is ready
Water is boiled
Breakfast ready in 3 seconds

What changed?

Synchronous versionAsynchronous version
static void Main()static async Task Main()
Thread.Sleep(3000) blocks the threadawait Task.Delay(3000) waits without blocking
Methods return voidMethods return Task (a "promise" of work that will finish later)
Each task starts after the previous one endsAll tasks start almost together
Total time: 6 secondsTotal time: 3 seconds (the longest task)

Look at the output order: all three tasks start first. Then they finish in the order of their duration: juice (1 s), toast (2 s), water (3 s). The program did not stand still while the water was boiling.

What is a Task?

A Task is an object that represents work that may finish in the future. You can think of it as an order receipt in a restaurant: you don't have the food yet, but you have a receipt that says "your food is being prepared". Later you use the receipt to collect the food.

  • Task means "some work, with no result value" (like a void method).
  • Task<int>, Task<string> and so on mean "some work that will give you a value of that type later".

Where asynchronous programming is used

  • Database calls: await connection.OpenAsync(), await command.ExecuteReaderAsync(), await context.Products.ToListAsync()
  • Web API calls: await httpClient.GetStringAsync(url)
  • Files: await File.ReadAllTextAsync(path), await File.WriteAllTextAsync(path, text)
  • Desktop apps: keep the window responsive while loading data.
  • ASP.NET Core: free the request thread while waiting for the database, so the server can serve more users.

An important point: async does not make a task faster

Boiling water still takes 3 seconds in the async version. Asynchronous programming does not make one single task faster. It helps because:

  • Waiting times of independent tasks can overlap, so the total time goes down.
  • The thread is free while waiting, so it can do other useful work (draw the screen, serve another web request).

Summary

  • Asynchronous programming lets you start a long task and continue with other work while it runs.
  • In C#, asynchronous work is represented by Task and Task<T>.
  • async and await are the C# keywords that make asynchronous code easy to write.
  • Waiting times of independent tasks overlap, so total time is often much shorter.
  • It is mostly used for work that waits: databases, files, web APIs and network calls.
  • It does not make a single task faster; it stops your program from wasting time while waiting.