In C# async programming, “block” and “suspend” refer to very different behaviors in how code waits for something to complete. In C#, await suspends the method without blocking the thread—that’s the magic of async/await.


What do Blocking and Suspending mean?

What Blocking Means

  • The thread stops executing and does nothing until the task finishes.
  • Other operations that could have used the thread must wait.

Example (Blocking):

Thread.Sleep(2000);  // Blocks the thread for 2 seconds

Or:

Task task = Task.Delay(2000);
task.Wait();  // Blocks the calling thread until task finishes

Impact:

  • Wastes a thread doing nothing.
  • Can freeze the UI in desktop apps.
  • Limits scalability in server apps.

What Suspending Means:

  • The async method pauses execution, but the thread is released to do other work.
  • When the awaited task completes, the method resumes from where it left off—possibly on a different thread.

Example (Suspending):

await Task.Delay(2000);  // Suspends method, but not the thread

Impact:

  • Non-blocking: Frees up the thread.
  • Better scalability and responsiveness.
  • Preferred in UI apps and web servers.

Breakdown of async/await program

Here is a simple C# console program we used earlier demonstrating how async/await works.

static async Task Main()
{
    Console.WriteLine("Start");
    await DoWorkAsync();       // Execution pauses here (suspends)
    Console.WriteLine("End");  // Resumes after DoWorkAsync() completes
}

static async Task DoWorkAsync()
{
    Console.WriteLine("Working...");
    await Task.Delay(2000);    // Suspends DoWorkAsync() for 2 seconds
    Console.WriteLine("Done!");
}

Output (with approximate timing)

Start
Working...
(wait 2 seconds...)
Done!
End

What Happens (Step-by-Step)

  1. Main() starts running

    • "Start" is printed.
    • Calls DoWorkAsync() and awaits it.
  2. DoWorkAsync() starts

    • "Working..." is printed.

    • Hits await Task.Delay(2000):

      • Execution suspends, method exits temporarily.
      • The thread is freed and can do other work (e.g. process UI events or handle another request).
  3. 2 seconds later

    • The delayed task completes.
    • Execution resumes inside DoWorkAsync().
    • "Done!" is printed.
    • Control returns to Main(), which prints "End".

More details about what happens at await DoWorkAsync();

static async Task Main()
{
    Console.WriteLine("Start");
    await DoWorkAsync();  // 👈 suspends Main here
    Console.WriteLine("End");
}

Why Main() can’t continue until DoWorkAsync() completes

The answer to this question goes right to the core of how await and async methods really work in C#.


Main() can’t continue until DoWorkAsync() completes, because we used:

await DoWorkAsync();  // you're awaiting the result

You’re telling Main() to wait (asynchronously) for DoWorkAsync() to finish before continuing — so even though the thread is free, the method execution (i.e., Main()) is suspended until DoWorkAsync() completes.

So:

The thread is free to do other work. But it doesn’t print "End" yet, because Main() is not resumed until DoWorkAsync() completes.


What If You Didn’t Await?

If you changed this:

await DoWorkAsync();

to:

_ = DoWorkAsync();  // fire and forget

Then Main() wouldn’t wait — it would continue and print "End" immediately:

Here is the output:

Start
Working...
End
Done!

What Does “Other Work” Mean?

Let’s break down what “other work” means when we say:

“The thread is freed and can do other work” in async programming (like C# with await).

It refers to any other task or piece of code that the system (thread pool, event loop, or UI thread) can execute while the current async method is suspended.

When an async method hits an await, the method suspends and returns control. The current thread is released, and the runtime is free to use it for:


🖥️ In a UI Application (e.g., WPF, WinForms)

  • The main thread is the UI thread.

  • While your async method is waiting (await Task.Delay):

    • The thread can:

      • Process user input (clicks, keyboard).
      • Redraw the screen.
      • Handle timers, animations.
      • Respond to drag & drop or OS events.

This is why async/await keeps the UI responsive — no freezing.


🌐 In a Web Server (e.g., ASP.NET Core)

  • The thread may be part of a thread pool handling HTTP requests.

  • When a controller hits await (e.g., calling a database or API):

    • The thread is released to:

      • Handle another incoming request.
      • Serve static files.
      • Process a different controller action.
      • Run scheduled background tasks.

This enables high scalability with fewer threads.


🧪 In a Console App / CLI Tool

  • The thread may return to the thread pool.

  • It can be reused to:

    • Start or resume other async methods.
    • Run Task.Run(...) background work.
    • Complete previously scheduled tasks.
    • Handle timers, completions, etc.

You see this when a single thread runs multiple async methods in sequence — interleaving them.


📌 Console.WriteLine("End"); is not “other work”!

When I first studied async programming, I am a little bit confused by the statement “The thread is freed and can do other work”. Why the thread won’t do “other work”, such as continue to printing “end”?

The issue is that continuing to print “end” (Console.WriteLine("End");) is not “other work”, it’s part of Main(), and Main() is suspended!


What happens to the thread when Main() is suspended?

DoWorkAsync() gives control back to Main()

In our example,

  1. await suspends the method at Task.Delay(), and the thread is freed.
  2. While the method is waiting (suspended), control is given back to the main thread or other tasks, like Main().
  3. Once the 2-second timer (from Task.Delay) finishes, control resumes inside DoWorkAsync(), and it prints “Done!”.

In other words, When DoWorkAsync() suspends, it gives control back to Main().


When Main() suspends, who gets control?

Answer: Control returns to the runtime — specifically, the thread pool or synchronization context.

So control is not passed to a method, but rather:

  • It’s returned to the runtime scheduler (like the ThreadPool or SynchronizationContext).

Then what happens to the thread?

🔄 When await Task.Delay(2000) suspends, the method pauses. The current thread is returned to the thread pool, but the execution context (the state) of DoWorkAsync() and Main() is stored.