Question

How and When to Use async and await in C#

csharp.netasynchronousasync-await

Question

I am trying to understand how async and await work in C#.

From what I understand, one of their main benefits is making asynchronous code easier to write and read. However, does using async and await mean that long-running work is automatically moved to a background thread?

I am testing this simple example and added comments to explain what confuses me:

private async void button1_Click(object sender, EventArgs e)
{
    Task<int> access = DoSomethingAsync();
    // Task-independent work here

    // Why is this line only reached after the 5-second delay in
    // DoSomethingAsync()? Shouldn't it run immediately?
    int a = 1;

    // I thought the waiting would happen here.
    int x = await access;
}

async Task<int> DoSomethingAsync()
{
    // Is this executed on a background thread?
    System.Threading.Thread.Sleep(5000);
    return 1;
}

Can you explain:

  • why button1_Click must be marked async
  • whether DoSomethingAsync() runs on a background thread
  • why execution does not continue immediately after calling DoSomethingAsync()
  • what await is actually doing in this example

Short Answer

By the end of this page, you will understand that async and await in C# do not automatically create background threads. You will learn what async really means, what await pauses, why Thread.Sleep() blocks execution, and how to write truly asynchronous code using APIs like Task.Delay() instead of blocking calls.

Concept

What async and await really do

In C#, async and await are language features for working with asynchronous operations. They help you write code that looks sequential while allowing the program to avoid blocking the current thread when possible.

A very common beginner misunderstanding is this:

  • Myth: async automatically runs code on another thread.
  • Reality: async by itself does not start a new thread.

What async does

Marking a method with async allows you to use await inside it. That is its main purpose.

For example:

async Task<int> GetValueAsync()
{
    int value = await SomeOperationAsync();
    return value;
}

Without async, you cannot use await inside the method body.

What await does

await says:

  • if the task is already finished, continue immediately
  • if the task is not finished, pause this method here
  • return control to the caller
  • resume this method later when the task completes

This means await does not block in the same way Thread.Sleep() does.

Why your example blocks

Your method is named DoSomethingAsync, but it does not actually perform asynchronous work:

async Task<int> DoSomethingAsync()
{
    System.Threading.Thread.Sleep(5000);
    return 1;
}

Thread.Sleep(5000) blocks the current thread for 5 seconds.

So when this method starts running, it executes synchronously until it reaches an await. But there is no await inside it. That means the whole method runs immediately on the current thread and blocks it.

In a UI app, that current thread is usually the UI thread, so the interface freezes.

Important rule

An async method runs synchronously until it reaches its first incomplete await.

If it never reaches an incomplete await, then it behaves like normal synchronous code.

Correct asynchronous version

If you want a non-blocking delay, use Task.Delay:

async Task<int> DoSomethingAsync()
{
    await Task.Delay(5000);
    return 1;
}

Now the method starts, reaches await Task.Delay(5000), and pauses there without blocking the UI thread.

Then code after the call site can continue until it reaches its own await.

Why this matters in real programs

Asynchronous programming is useful when waiting for:

  • HTTP requests
  • database queries
  • file I/O
  • timers and delays
  • network communication

These operations spend time waiting, so it is wasteful to block a thread for them. async/await lets the application stay responsive while waiting.

Mental Model

Think of async/await like ordering food at a restaurant.

  • Synchronous code: You stand at the counter and refuse to move until your food is ready. Nobody else can use your spot.
  • Asynchronous code: You place your order, get a buzzer, and step aside. When the food is ready, the buzzer tells you to come back.

Now apply that to your code:

  • Task is the buzzer ticket.
  • await is stepping aside until the food is ready.
  • Thread.Sleep() is standing still at the counter for 5 seconds and blocking everyone behind you.

So await is not “make a new worker.” It is more like “pause here and come back later when the work finishes.”

If the work itself is just blocking the current thread, like Thread.Sleep(), nothing is really gained.

Take Quiz

Syntax and Examples

Basic syntax

async Task MyMethodAsync()
{
    await SomeTaskReturningMethodAsync();
}

Methods that commonly use async

async Task DoWorkAsync()
{
    await Task.Delay(1000);
}

async Task<int> GetNumberAsync()
{
    await Task.Delay(1000);
    return 42;
}

async void button1_Click(object sender, EventArgs e)
{
    await DoWorkAsync();
}

Return type guide

  • async Task → async method with no result
  • async Task<T> → async method that returns a value
  • async void → usually only for event handlers

Your original example fixed

private async void button1_Click(object sender, EventArgs e)
{
    Task<int> access = DoSomethingAsync();

    // This runs immediately now
    int a = 1;

    int x = await access;
}

async Task<int> DoSomethingAsync()
{
    await Task.Delay(5000);
    return 1;
}

What changed?

  • Task.Delay(5000) creates an asynchronous wait.
  • DoSomethingAsync() reaches await, pauses, and returns a Task<int> quickly.
  • int a = 1; executes immediately.
  • await access waits later for the result.

If you really need CPU work on a background thread

For CPU-bound work, use Task.Run:

async Task<int> CalculateAsync()
{
    return await Task.Run(() =>
    {
        Thread.Sleep(5000);
        return 1;
    });
}

This does use a thread pool thread, but that is different from ordinary async I/O.

Step by Step Execution

Example to trace

private async void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("Click start");

    Task<int> task = DoSomethingAsync();

    Console.WriteLine("After calling DoSomethingAsync");

    int x = await task;

    Console.WriteLine($"Result: {x}");
}

async Task<int> DoSomethingAsync()
{
    Console.WriteLine("DoSomethingAsync started");
    await Task.Delay(2000);
    Console.WriteLine("DoSomethingAsync finished");
    return 1;
}

What happens step by step

  1. The button is clicked.
  2. button1_Click starts running on the UI thread.
  3. Console.WriteLine("Click start") runs.
  4. DoSomethingAsync() is called.
  5. Inside DoSomethingAsync, Console.WriteLine("DoSomethingAsync started") runs immediately.
  6. The method reaches await Task.Delay(2000).
  7. Because the delay is not complete yet, DoSomethingAsync pauses and returns a Task<int> to the caller.
  8. Back in button1_Click, Console.WriteLine("After calling DoSomethingAsync") runs immediately.
  9. Execution reaches await task.
  10. Since the task is not finished yet, button1_Click also pauses.
  11. After 2 seconds, the delay completes.
  12. DoSomethingAsync resumes, prints DoSomethingAsync finished, and returns 1.
  13. The awaited task in button1_Click completes.
  14. button1_Click resumes and prints Result: 1.

Output order

Click start
DoSomethingAsync started
After calling DoSomethingAsync
DoSomethingAsync finished
Result: 1

Contrast with Thread.Sleep

If you replace await Task.Delay(2000) with Thread.Sleep(2000), then step 7 never happens early. The method does not pause asynchronously. It blocks the thread for 2 seconds before returning anything useful.

Real World Use Cases

Where async/await is useful

Web requests

var response = await httpClient.GetAsync(url);

The app can do other work while waiting for the server.

Reading files

string text = await File.ReadAllTextAsync(path);

Useful when loading large files without freezing a UI.

Database queries

var user = await dbContext.Users.FindAsync(id);

Common in ASP.NET and desktop apps.

UI event handlers

private async void loadButton_Click(object sender, EventArgs e)
{
    statusLabel.Text = "Loading...";
    var data = await LoadDataAsync();
    statusLabel.Text = "Done";
}

The UI stays responsive while data loads.

Delays and retry logic

await Task.Delay(1000);

Useful for polling, retrying failed requests, or scheduling waits without blocking threads.

Take Quiz

Real Codebase Usage

How this concept is used in real projects

Async all the way

If one method awaits another, the calling method often becomes async too.

public async Task SaveAsync()
{
    await repository.SaveAsync();
}

This avoids blocking with .Result or .Wait().

Event handlers use async void

In UI frameworks, event handlers are one of the few correct places for async void.

private async void submitButton_Click(object sender, EventArgs e)
{
    await SubmitFormAsync();
}

Guard clauses before awaiting

public async Task<string> LoadUserAsync(int id)
{
    if (id <= 0)
        throw new ArgumentException("Invalid id");

    return await api.GetUserAsync(id);
}

Validate early, then await.

Error handling with try/catch

try
{
    var data = await service.GetDataAsync();
}
catch (HttpRequestException ex)
{
    Console.WriteLine(ex.Message);
}

Exceptions from awaited tasks are re-thrown at the await point.

Non-blocking delays instead of sleeping

await Task.Delay(500);

Real codebases prefer this over Thread.Sleep() in async methods.

CPU-bound work with Task.Run

var result = await Task.Run(() => ExpensiveCalculation());

Use this when you actually want background CPU work, especially in UI apps.

Common Mistakes

1. Thinking async creates a new thread

Broken assumption:

async Task DoWorkAsync()
{
    Thread.Sleep(5000);
}

Why it is wrong:

  • async does not move this to another thread
  • Thread.Sleep still blocks the current thread

Better:

async Task DoWorkAsync()
{
    await Task.Delay(5000);
}

2. Marking a method async without await

async Task<int> GetNumberAsync()
{
    return 1;
}

This compiles with a warning and is usually unnecessary.

Better:

Task<int> GetNumberAsync()
{
    return Task.FromResult(1);
}

3. Using async void for normal methods

Broken pattern:

async void LoadData()
{
    await Task.Delay(1000);
}

Why it is bad:

  • caller cannot await it
  • exceptions are harder to handle

Better:

async Task LoadDataAsync()
{
    await Task.Delay(1000);
}

4. Blocking async code with .Wait() or .Result

var result = DoSomethingAsync().Result;

This can freeze apps or even deadlock in some environments.

Better:

var result = await DoSomethingAsync();

5. Assuming code after a method call always runs immediately

If the called method does blocking work before its first await, the caller may not continue right away.

Task<int> task = DoSomethingAsync();
int a = 1; // may be delayed if DoSomethingAsync blocks first

Avoid this by making the called method truly asynchronous.

6. Confusing CPU-bound and I/O-bound work

  • I/O-bound: use async APIs like Task.Delay, ReadAllTextAsync, GetAsync
  • CPU-bound: use Task.Run if background execution is needed

Comparisons

Related concepts compared

ConceptWhat it doesBlocks thread?Creates new thread automatically?Best for
asyncAllows await in a methodNo, by itselfNoWriting async methods
awaitPauses method until task completesNoNoWaiting for async operations
Thread.Sleep()Stops current thread for a timeYesNoRarely useful in async code
Task.Delay()Completes a task after a timeNoNoNon-blocking delays
Task.Run()Queues work to thread poolUsually no for callerYes, usually thread poolCPU-bound work

Task.Delay() vs Thread.Sleep()

FeatureTask.Delay()Thread.Sleep()
Non-blockingYesNo
Good in async methodsYesNo
Freezes UI thread if used thereNoYes
Can be awaitedYesNo

async Task vs async void

Return typeUse whenCan be awaited?Exceptions easier to handle?
async TaskNormal async methodsYesYes
async Task<T>Async methods returning dataYesYes
async voidEvent handlers onlyNoNo
Take Quiz

Cheat Sheet

Quick rules

  • async lets you use await
  • await pauses the method, not the whole program
  • async does not automatically create a new thread
  • An async method runs synchronously until its first incomplete await
  • Thread.Sleep() blocks the current thread
  • Task.Delay() is the async, non-blocking alternative
  • Use async Task or async Task<T> for most async methods
  • Use async void mainly for event handlers
  • Avoid .Wait() and .Result when possible

Common patterns

async Task DoWorkAsync()
{
    await Task.Delay(1000);
}
async Task<int> GetValueAsync()
{
    await Task.Delay(1000);
    return 42;
}
private async void button_Click(object sender, EventArgs e)
{
    int value = await GetValueAsync();
}

If you need a delay

await Task.Delay(5000);

If you need background CPU work

int result = await Task.Run(() => ExpensiveCalculation());

Warning signs

  • async method with no await
  • Thread.Sleep() inside async code
  • .Result or .Wait() on tasks
  • using async void outside event handlers

FAQ

Why does code before await still run immediately?

Because an async method starts executing normally. It only pauses when it reaches an incomplete awaited task.

Does await create a new thread?

No. await waits for a task to finish without blocking the current thread. It does not itself create threads.

Why did int a = 1; not run immediately in the example?

Because DoSomethingAsync() used Thread.Sleep(5000), which blocked the current thread before returning control.

Why must button1_Click be marked async?

Because it uses await access;. A method must be marked async if it contains await.

Is async void bad?

Usually yes for normal methods, but it is appropriate for event handlers in UI code.

What should I use instead of Thread.Sleep() in async code?

Use await Task.Delay(...) for delays, or use truly asynchronous APIs for I/O operations.

When should I use Task.Run()?

Use it when you need to move CPU-bound work off the UI thread or off the request thread. Do not use it just to wrap ordinary async I/O.

Take Quiz

Mini Project

Description

Build a small desktop-style simulation that shows the difference between blocking and non-blocking delays. The project demonstrates why Thread.Sleep() freezes progress while await Task.Delay() allows the program to remain responsive.

Goal

Create a simple C# program that starts an asynchronous operation, does other work immediately, and then awaits the final result.

Requirements

  • Create a method that returns Task<int> after a non-blocking delay.
  • Call the method and store the returned task before awaiting it.
  • Print or log a message immediately after starting the task.
  • Await the task later and print the returned result.
  • Do not use Thread.Sleep() inside the asynchronous method.
Take Quiz

Keep learning

Related questions

View more