← Back to Articles         
Difference between Task.Run() and Task.Delay()

Difference between Task.Run() and Task.Delay()

Published on 12 Oct 2026     7 min read C#
Asynchronous Programming (Async/Await)

C# Asynchronous Programming: Difference Between Task.Run() and Task.Delay()

1. What are Task.Run() and Task.Delay()?

Both Task.Run() and Task.Delay() are methods provided by the System.Threading.Tasks namespace, but they serve completely different purposes.

  • Task.Run() – Executes a delegate on a ThreadPool thread, typically for CPU-bound operations.

  • Task.Delay() – Creates a task that completes after a specified time without blocking a thread during the wait.

Main Difference:

Task.Run() is used to execute work asynchronously on the ThreadPool, whereas Task.Delay() is used to introduce a non-blocking asynchronous delay.

2. Comparison Table

Feature

Task.Run()

Task.Delay()

Purpose

Execute work on ThreadPool

Introduce an asynchronous delay

Thread usage

Uses ThreadPool thread

No thread occupied while waiting

CPU usage

Depends on the work

Minimal

Blocking

Work occupies a worker thread

Does not block a thread

Typical use

CPU-bound operations

Delays, retry intervals

Returns

Task or Task<T>

Task

Can use await

Yes

Yes

Executes code

Yes

No application delegate

Example

Task.Run(() => Calculate())

Task.Delay(2000)

 

3. How Does Task.Run() Work?

Task.Run() schedules a method or delegate to execute on a ThreadPool thread.

It is commonly used to move CPU-intensive work away from the calling thread, particularly in desktop applications.

Example Program

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

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Main Started");

        await Task.Run(() =>
        {
            Console.WriteLine("Processing Started");

            Thread.Sleep(2000);

            Console.WriteLine("Processing Completed");
        });

        Console.WriteLine("Main Completed");
    }
}

Output

Main Started
Processing Started
(Wait 2 seconds)
Processing Completed
Main Completed

Step-by-Step Explanation

  1. Main() starts executing.

  2. Task.Run() schedules the lambda expression on a ThreadPool thread.

  3. The ThreadPool thread prints Processing Started.

  4. Thread.Sleep(2000) blocks that worker thread for two seconds.

  5. The worker thread prints Processing Completed.

  6. The task completes, and Main() continues after await.

  7. Main Completed is printed.

Here, Thread.Sleep() is used only to demonstrate that the work occupies a worker thread. Real CPU-bound operations would perform calculations or data processing instead.

Execution Flow

Main Thread
     |
     v
Task.Run()
     |
     v
Schedule work on ThreadPool
     |
     +----------------------------+
     |                            |
     v                            v
Main awaits Task           ThreadPool Thread
                                  |
                                  v
                           Execute Method
                                  |
                                  v
                           Processing Work
                                  |
                                  v
                           Work Completed
                                  |
                                  v
                           Task Completed
     |                            |
     +<---------------------------+
     |
     v
Main Continues

Key Points

  • Task.Run() schedules work on the .NET ThreadPool.

  • It is useful for CPU-bound operations such as complex calculations, image processing, and data transformations.

  • The calling thread does not execute the delegate directly.

  • await Task.Run() asynchronously waits for the scheduled work to finish.

  • Task.Run() does not automatically make the underlying operation faster.

  • In ASP.NET Core, avoid wrapping naturally asynchronous database or HTTP operations in Task.Run().

 

4. How Does Task.Delay() Work?

Task.Delay() creates a task that completes after a specified duration.

Unlike Thread.Sleep(), it does not block a thread while waiting.

Example Program

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Main Started");

        await Task.Delay(2000);

        Console.WriteLine("Main Completed");
    }
}

Output

Main Started
(Wait 2 seconds)
Main Completed

Step-by-Step Explanation

  1. Main() starts executing.

  2. Task.Delay(2000) creates a task representing a two-second delay.

  3. await checks whether the task has completed.

  4. Since the delay is incomplete, the asynchronous method suspends.

  5. No thread is dedicated to waiting for the timer.

  6. After approximately two seconds, the task completes.

  7. Execution resumes and prints Main Completed.

Execution Flow

Main()
   |
   v
Task.Delay(2000)
   |
   v
Timer Registered
   |
   v
await suspends Main()
   |
   v
No Thread Blocked
   |
   v
Timer Expires
   |
   v
Task Completed
   |
   v
Main() Resumes

Key Points

  • Task.Delay() is a non-blocking asynchronous delay.

  • It uses timer infrastructure rather than dedicating a worker thread to wait.

  • It is useful for retry intervals, polling, and throttling.

  • It does not execute a CPU-intensive operation.

  • It supports cancellation using a CancellationToken.

 

5. Practical Example: Task.Run() vs Task.Delay()

The following example demonstrates both methods in one program.

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

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Application Started");

        // Task.Run - Executes work on ThreadPool
        await Task.Run(() =>
        {
            Console.WriteLine("Task.Run Started");

            Console.WriteLine(
                $"Worker Thread ID: {Environment.CurrentManagedThreadId}");

            Thread.Sleep(2000);

            Console.WriteLine("Task.Run Completed");
        });

        // Task.Delay - Non-blocking delay
        Console.WriteLine("Task.Delay Started");

        await Task.Delay(2000);

        Console.WriteLine("Task.Delay Completed");

        Console.WriteLine("Application Completed");
    }
}

Sample Output

Application Started
Task.Run Started
Worker Thread ID: 4
Task.Run Completed
Task.Delay Started
(Wait 2 seconds)
Task.Delay Completed
Application Completed

Explanation

During Task.Run(): A ThreadPool thread executes the provided delegate. In this example, the worker thread is blocked by Thread.Sleep().

During Task.Delay(): The asynchronous method waits for a timer to complete without occupying a thread during the waiting period.

The total execution time is approximately four seconds because the operations are awaited sequentially.

 

6. Real-World Scenario: ASP.NET Core Web API

Consider an ASP.NET Core application that performs two types of operations.

Scenario A: CPU-Bound Processing

Suppose you need to perform a complex calculation.

public async Task<long> CalculateAsync()
{
    return await Task.Run(() =>
    {
        long total = 0;

        for (long i = 0; i < 10_000_000; i++)
        {
            total += i;
        }

        return total;
    });
}

Explanation:

  • Task.Run() schedules the calculation on a ThreadPool thread.

  • The operation consumes CPU resources.

  • The caller can asynchronously await the result.

  • This is useful in desktop applications when the UI must remain responsive.

Important: In ASP.NET Core, Task.Run() generally should not be used merely to move CPU-bound request processing to another ThreadPool thread. ASP.NET Core already executes request processing on ThreadPool threads. For expensive or long-running jobs, a background processing service or job queue may be more appropriate.

Scenario B: Retry an API Call After a Delay

Suppose an external API temporarily fails, and you want to retry after two seconds.

public async Task CallApiWithRetryAsync()
{
    for (int attempt = 1; attempt <= 3; attempt++)
    {
        try
        {
            await CallExternalApiAsync();

            return;
        }
        catch (HttpRequestException) when (attempt < 3)
        {
            Console.WriteLine("Retrying after 2 seconds...");

            await Task.Delay(2000);
        }
    }
}

Assume CallExternalApiAsync() is an existing method that makes an asynchronous HTTP request.

Explanation:

  1. The application calls the external API.

  2. If the request fails with an HttpRequestException, it waits for two seconds.

  3. The wait does not block a thread.

  4. After the delay, the application retries.

  5. The operation attempts the request a maximum of three times.

In production, use cancellation tokens, retry backoff, and appropriate handling for transient failures.

Key Points

  • Use Task.Run() primarily when you need to offload CPU-bound work from a thread that must remain responsive.

  • Use Task.Delay() for non-blocking waiting.

  • For asynchronous SQL Server queries, HTTP requests, and file I/O, prefer their native asynchronous APIs rather than wrapping them in Task.Run().

 

7. Difference Between Task.Delay() and Thread.Sleep()

This is another common C# interview question.

Feature

Task.Delay()

Thread.Sleep()

Blocking

Non-blocking when awaited

Blocks current thread

Thread occupied during wait

No

Yes

Asynchronous

Yes

No

Return type

Task

void

Cancellation support

Yes

No cancellation-token overload

Recommended for async methods

Yes

No

Example:

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

// Non-blocking asynchronous delay
await Task.Delay(2000);

Execution Difference

Thread.Sleep(2000)
        |
        v
Current Thread Blocked
        |
        v
Wait 2 Seconds
        |
        v
Thread Continues


await Task.Delay(2000)
        |
        v
Timer Registered
        |
        v
Async Method Suspended
        |
        v
No Thread Occupied Waiting
        |
        v
Timer Completes
        |
        v
Method Resumes
 

8. Important Interview Question: Does Task.Run() Always Create a New Thread?

No.

Task.Run() schedules work on the .NET ThreadPool. It generally reuses an existing ThreadPool thread rather than creating a dedicated new thread.

For example:

await Task.Run(() =>
{
    Console.WriteLine(
        $"Thread ID: {Environment.CurrentManagedThreadId}");
});

The ThreadPool manages worker threads and may create additional threads when necessary.

Also, Task.Run() does not guarantee that every invocation executes on a different thread.

 

9. When Should You Use Each?

Requirement

Recommended

Complex CPU calculation in a desktop application

Task.Run()

Keep a desktop UI responsive during CPU work

Task.Run()

Wait two seconds asynchronously

Task.Delay()

Retry an API request after a delay

Task.Delay()

Implement periodic polling

Task.Delay() or PeriodicTimer

Fetch records using EF Core

ToListAsync() / FirstOrDefaultAsync()

Call an external REST API

HttpClient.GetAsync()

Execute a long-running background job in ASP.NET Core

Background service / job queue

10. Final Interview Summary

Task.Run() schedules a delegate for execution on a ThreadPool thread. It is mainly useful for offloading CPU-bound work, especially in UI applications.

Task.Delay() creates a non-blocking asynchronous wait for a specified duration. It does not dedicate a thread to waiting.

Remember these three statements:

  • Task.Run() = Execute work on ThreadPool.

  • Task.Delay() = Wait asynchronously without blocking a thread.

  • Thread.Sleep() = Block the current thread for a duration.

The most important distinction is that Task.Run() performs work, while Task.Delay() only represents the passage of time.

← Back to Articles