Skip to content

What is Blocking and Non-Blocking Execution?

The words blocking and non-blocking come up all the time when people talk about async code. They describe what a thread does while it waits.

First, what is a thread?

A thread is a worker that runs your code, line by line. Every program has at least one thread (the main thread). A desktop app has a special UI thread that draws the window and handles clicks. A web server has a pool of threads that handle incoming requests.

Blocking execution

Blocking means the thread stops and waits for an operation to finish, and it cannot do anything else during that time.

You call a customer care number and they put you on hold for 10 minutes. You are holding the phone, you can't leave, you can't make another call. You are blocked.

Common blocking calls in C#:

  • Thread.Sleep(1000)
  • task.Wait()
  • task.Result
  • task.GetAwaiter().GetResult()
  • Synchronous I/O methods such as File.ReadAllText or command.ExecuteReader()

Non-blocking execution

Non-blocking means the thread starts the operation and is then free. It goes back to do other work. When the operation finishes, the rest of the code continues.

You send an order on a food delivery app. You don't stay on the phone; you get a notification when the food arrives. Meanwhile you are free. That is non-blocking.

In C#, the main tool for non-blocking waiting is await:

  • await Task.Delay(1000) instead of Thread.Sleep(1000)
  • await task instead of task.Wait() or task.Result
  • await File.ReadAllTextAsync(path) instead of File.ReadAllText(path)

Example 1: the thread is free while waiting

In this program the report "downloads" for 2 seconds. Because we don't block, Main keeps doing other work during the download.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Task<string> downloadTask = DownloadReportAsync();

        // Main is free while the report is downloading
        while (!downloadTask.IsCompleted)
        {
            Console.WriteLine("Doing other work...");
            await Task.Delay(500);
        }

        string report = await downloadTask;
        Console.WriteLine(report);
    }

    static async Task<string> DownloadReportAsync()
    {
        await Task.Delay(2000);   // pretend this is a download
        return "Report downloaded";
    }
}
Output
Doing other work...
Doing other work...
Doing other work...
Doing other work...
Report downloaded

"Doing other work..." appears about four times (it can be one more or less, depending on timing). The important point is that Main was never stuck while the download was running.

Now change one line to block:

string report = DownloadReportAsync().Result;   // blocks for 2 seconds
Console.WriteLine(report);

This time the thread just sits for 2 seconds and does nothing else. The result is the same, but the thread was wasted.

Example 2: a frozen window

Blocking is easiest to see in a desktop app. Both button handlers below wait 5 seconds.

// Blocking: the window freezes for 5 seconds
private void btnLoad_Click(object sender, EventArgs e)
{
    lblStatus.Text = "Loading...";
    Thread.Sleep(5000);
    lblStatus.Text = "Done";
}

// Non-blocking: the window stays responsive
private async void btnLoad_Click(object sender, EventArgs e)
{
    lblStatus.Text = "Loading...";
    await Task.Delay(5000);
    lblStatus.Text = "Done";
}
  • In the blocking version, the UI thread is stuck. You can't move or resize the window, and Windows may show "Not Responding". You may not even see the text "Loading...", because the thread is too busy to repaint the label.
  • In the non-blocking version, "Loading..." appears immediately, the window can be moved, and after 5 seconds the label changes to "Done".

Example 3: blocking on a web server

In ASP.NET Core, every request is handled by a thread from the thread pool.

// Blocking: the thread waits for SQL Server and can't serve anyone else
public IActionResult GetOrders()
{
    var orders = _context.Orders.ToList();
    return Ok(orders);
}

// Non-blocking: the thread goes back to the pool while SQL Server works
public async Task<IActionResult> GetOrders()
{
    var orders = await _context.Orders.ToListAsync();
    return Ok(orders);
}

With a few users there is no visible difference. With thousands of users, the blocking version runs out of free threads and requests start to queue up, while the non-blocking version keeps serving.

Blocking vs non-blocking at a glance

BlockingNon-blocking
The thread waits and does nothing elseThe thread is released and does other work
Thread.Sleep, .Wait(), .Resultawait Task.Delay, await task
UI freezesUI stays responsive
Server threads get used upServer threads are reused
Can cause deadlocks when mixed with async codeNo deadlock from waiting

Golden rule: do not block on async code. If a method gives you a Task, await it instead of calling .Result or .Wait(). Blocking on async code can freeze the app forever (a deadlock), especially in WinForms, WPF and old ASP.NET applications. We explain why in the lesson on benefits and limitations.

Summary

  • Blocking: the thread waits and can't do anything else.
  • Non-blocking: the thread starts the work, is released, and continues later.
  • Thread.Sleep, .Wait() and .Result block; await does not.
  • Blocking freezes desktop apps and wastes threads on web servers.
  • Prefer await whenever you have a Task.