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)
-
Main()
starts running"Start"
is printed.- Calls
DoWorkAsync()
andawait
s it.
-
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).
-
-
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,
await
suspends the method atTask.Delay()
, and the thread is freed.- While the method is waiting (suspended), control is given back to the main thread or other tasks, like
Main()
. - Once the 2-second timer (from
Task.Delay
) finishes, control resumes insideDoWorkAsync()
, 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.