614

Are there good rule(s) for when to use Task.Delay versus Thread.Sleep?

  • Specifically, is there a minimum value to provide for one to be effective/efficient over the other?
  • Lastly, since Task.Delay causes context-switching on a async/await state machine, is there an overhead of using it?
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Tom K.
  • 6,549
  • 3
  • 14
  • 14
  • 4
    10ms is a lot of cycles in computer world... – Brad Christie Nov 19 '13 at 21:21
  • How fast should it be? What performance problems do you have? – L.B Nov 19 '13 at 21:28
  • 4
    I think the more pertinent question is in what context do you intend to use either of these? Without that information the scope is too broad. What do you mean by effective/efficient? Are you referring to accuracy, power efficiency etc.? I'm very curious to know in what context this matters. – James World Nov 19 '13 at 21:40
  • 5
    The minimum is 15.625 msec, values less than the clock interrupt rate have no effect. Task.Delay always burns up a System.Threading.Timer, Sleep has no overhead. You don't worry about overhead when you write code that does nothing. – Hans Passant Nov 19 '13 at 22:11
  • 4
    something that I didn't see mentioned, but I think is important, would be that Task.Delay supports a CancellationToken, meaning you can interrupt the delay, if you, for example, are using it to slow down a cycle process. this also means your process can respond quickly when you want to cancel it . but you can achieve the same with Thread.Sleep making the sleep cycle interval shorter, and check the Token manuallay. – Droa Dec 18 '19 at 08:32
  • https://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx – Mike Lowery Sep 11 '20 at 20:05

10 Answers10

598

Use Thread.Sleep when you want to block the current thread.

Use await Task.Delay when you want a logical delay without blocking the current thread.

Efficiency should not be a paramount concern with these methods. Their primary real-world use is as retry timers for I/O operations, which are on the order of seconds rather than milliseconds.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 4
    It's the same primary use case: a retry timer. – Stephen Cleary Nov 20 '13 at 00:42
  • 4
    Or when you don't want to chew up CPU in a main loop. – Eddie Parker May 20 '14 at 21:23
  • StephenCleary , But it _will_ block the other thread which is actually do "Delay " ...right? – Royi Namir Nov 03 '14 at 09:00
  • 7
    @RoyiNamir: No. There is no "other thread". Internally, it's implemented with a timer. – Stephen Cleary Nov 03 '14 at 18:34
  • @StephenCleary does that lack of blocking of a thread also apply when doing myTaskName.Wait() or does that cause any sort of system resource hogging/allocation? I'm trying to have one of my monitoring tools only perform operations every N minutes, and I want to make sure other processes aren't missing out on CPU thread availability while vying for resources just sitting seemingly idle. – kayleeFrye_onDeck Jan 21 '16 at 18:45
  • @StephenCleary : Do you suggest to use TaskDelay with await? If so when? Always or just only when working with UI thread? – CharithJ Jan 27 '16 at 04:58
  • @CharithJ: You should use `Task.Delay` whenever you need to do an asynchronous delay. – Stephen Cleary Jan 27 '16 at 14:29
  • 82
    The suggestion not to worry about efficiency is ill-advised. `Thread.Sleep` will block the current thread which causes a context switch. If you're using a thread pool this could also cause a new thread to be allocated. Both operations are quite heavy whereas the cooperative multi-tasking provided by `Task.Delay` etc is designed to avoid all of that overhead, maximize throughput, allow cancellation, and provide cleaner code. – Corillian May 03 '16 at 16:06
  • 2
    @StephenCleary: Do you suggest using `Thread.Sleep` instead of `Task.Delay(delay).Wait()` when we have to wait inside a synchronous method? I don't see any advantage in using `Task.Delay(delay).Wait()` in this case. – Luca Cremonesi Nov 30 '16 at 19:44
  • @StephenCleary: Speaking of which, I found [this answer](http://stackoverflow.com/a/34052540/1846281) regarding waiting a `Task.Delay` or calling `Thread.Sleep`, that I agree on. – Luca Cremonesi Nov 30 '16 at 20:03
  • 7
    @LucaCremry `onesi: I would use `Thread.Sleep` to wait inside a synchronous method. However, I never do this in production code; in my experience, every `Thread.Sleep` I've ever seen has been indicative of some kind of design issue that needs to be properly fixed. – Stephen Cleary Dec 01 '16 at 02:26
  • 1
    I think you mean `await Task.Delay(n);`. It doesn't do anything without the `await`. This is relevant to @CharithJ . – H2ONaCl Oct 14 '20 at 22:28
  • In an async program, multiple simultanously executing asynchronous methods will not be affected by a thread.sleep in one of their siblings. Asynchronous code does not create new threads and a call to thread.sleep does not put the parent to sleep. e.g. Child async methods that are not called immediately by an await that also call thread.sleep do not put their parent's code to sleep. So thread.sleep() works the same as task.delay() in asynchronous code until an await is called. At that point, I would assume the parent would go to sleep for whatever remaining time is left. Maybe not: async – Patrick Knott May 12 '22 at 19:54
  • 1
    @PatrickKnott: `Thread.Sleep` blocks the current thread until the sleep completes. This is true regardless of whether it's in a synchronous or asynchronous method. `await Task.Delay` yields until the delay completes, potentially not blocking a thread at all. – Stephen Cleary May 13 '22 at 01:02
  • 1
    @PatrickKnott: I recommend asking your own question. Comments aren't really the place for followup questions. – Stephen Cleary May 17 '22 at 17:21
  • I deleted most of my comments and asked a new question. https://stackoverflow.com/questions/72279745/how-does-async-programming-work-with-threads-when-using-thread-sleep – Patrick Knott May 17 '22 at 19:35
  • 1
    @StephenCleary the answer is somewhat misleading if I may say so. `Task.Delay(1000).Wait()` will also block the current thread. In fact, `Task.Delay(1000)` on itself doesn't do much that claim some memory which gets collected a second or so later. I know you know this. But maybe it's good to realize that not everybody masters these concepts. They look at this answer, see a shit ton of upvotes, and just blindly type `Task.Delay(1000).Wait()` or even omit the `Wait()`. Can't really blame them either – bas Sep 21 '22 at 09:24
  • 1
    @bas: Thanks for your comment! I've updated the answer to hopefully be more clear. – Stephen Cleary Sep 21 '22 at 11:01
373

The biggest difference between Task.Delay and Thread.Sleep is that Task.Delay is intended to run asynchronously. It does not make sense to use Task.Delay in synchronous code. It is a VERY bad idea to use Thread.Sleep in asynchronous code.

Normally you will call Task.Delay() with the await keyword:

await Task.Delay(5000);

or, if you want to run some code before the delay:

var sw = new Stopwatch();
sw.Start();
Task delay = Task.Delay(5000);
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
await delay;

Guess what this will print? Running for 0.0070048 seconds. If we move the await delay above the Console.WriteLine instead, it will print Running for 5.0020168 seconds.

Let's look at the difference with Thread.Sleep:

class Program
{
    static void Main(string[] args)
    {
        Task delay = asyncTask();
        syncCode();
        delay.Wait();
        Console.ReadLine();
    }

    static async Task asyncTask()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("async: Starting");
        Task delay = Task.Delay(5000);
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        await delay;
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("async: Done");
    }

    static void syncCode()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("sync: Starting");
        Thread.Sleep(5000);
        Console.WriteLine("sync: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("sync: Done");
    }
}

Try to predict what this will print...

async: Starting
async: Running for 0.0070048 seconds
sync: Starting
async: Running for 5.0119008 seconds
async: Done
sync: Running for 5.0020168 seconds
sync: Done

Also, it is interesting to notice that Thread.Sleep is far more accurate, ms accuracy is not really a problem, while Task.Delay can take 15-30ms minimal. The overhead on both functions is minimal compared to the ms accuracy they have (use Stopwatch Class if you need something more accurate). Thread.Sleep still ties up your Thread, Task.Delay release it to do other work while you wait.

Tarik
  • 79,711
  • 83
  • 236
  • 349
Dorus
  • 7,276
  • 1
  • 30
  • 36
  • 43
    Why is it "a VERY bad idea to use Thread.Sleep in asynchronous code"? – sunside Feb 26 '15 at 16:13
  • 124
    @sunside One of the major advantages of async code is to allow one thread to work on multiple tasks at once, by avoiding blocking calls. This avoids the need for huge amounts of individual threads, and allows a threadpool to service many requests at once. However, given that async code usually runs on the threadpool, needlessly blocking a single thread with `Thread.Sleep()` consumes an entire thread that could otherwise be used elsewhere. If many tasks are run with Thread.Sleep(), there's a high chance of exhausting all threadpool threads and seriously hindering performance. – Ryan Mar 02 '15 at 23:53
  • 1
    Get it. I was missing the notion of asynchronous code in the sense of `async` methods as they are encouraged to be used. It's basically just a bad idea to run `Thread.Sleep()` in a threadpool thread, not a bad idea in general. After all, there's `TaskCreationOptions.LongRunning` when going the (albeit discouraged) `Task.Factory.StartNew()` route. – sunside Mar 03 '15 at 08:58
  • 1
    You can use `Thread.Sleep` in async code for very low values, e.g. `Thread.Sleep(50)` (for some HW communication). – xmedeko Nov 13 '17 at 20:31
  • @Dorus "while Task.Delay can take 15-30ms minimal": Do you have anything to back up that claim? I mean, I can myself see that this indeed is the case in my code, but is there any documentation that supports this? Is there a general rule to **not** use `Task.Delay` for delays less than 15-30ms? – Reyhn Sep 06 '18 at 08:35
  • 4
    @Reyhn The documentation on this is that `Tasl.Delay` uses the system timer. Since "The system clock "ticks" at a constant rate.", the tick speed of the system timer is about 16ms, any delay you request will be rounded to a number of ticks of the system clock, offset by the time till the first tick. See the msdn documentation on `Task.Delay` https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay and scroll down to remarks. – Dorus Sep 13 '18 at 10:27
  • @Reyhn You can safely use shorter delays for `Task.Delay`, but notice a delay might end up 0ms (and run sync?), or get rounded up, so it will not be very precise. I have had better success by doing slightly more work in a single loop before calling `Task.Delay` with a longer delay instead. Also consider `Task.Yield`, as that will at least enforce an async call with minimal delay. – Dorus Sep 13 '18 at 10:34
  • I didn't understand what you are trying to show with your code.. in both cases they just wait 5 and some seconds.. you just added in the async code a writeline before the delay, and in the sync code you wrote the writeline only after the delay. So what's the point here? – CodeMonkey May 15 '19 at 08:03
  • 1
    @YonatanNir The point is that the wait does not happen until it hits `await`. It's a mistake i see very often in async code where people call `Task.Delay(5000)` but forget to `await` it. – Dorus May 16 '19 at 16:16
  • @YonatanNir Another point this demonstrate is how `await` mixes with other code on the caller. You can see the code before `await` in the asyncTask actually runs sync, but after it hits `await` it continues sync with the code in the `syncCode` function as that's the next thing that's called by `Main`. The code below `await` actually continues async in the background. – Dorus May 16 '19 at 16:19
  • @Dorus As I understand, when running in the background, there is no new thread. Then how is it done in the background? – CodeMonkey May 17 '19 at 12:09
  • @Dorus but it means it runs on a thread.. a blog post I read said that the task runs on a much lower level on device's drivers without involving any threads – CodeMonkey May 19 '19 at 11:43
  • @YonatanNir I'm not sure what blogs you read, but as far as i know, threads are, by default, scheduler on the [System.Threading.ThreadPool](https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool?view=netframework-4.8) unless you specify a scheduler with different behavior. I suggest you read the [TPL documentation](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) for more details. – Dorus May 19 '19 at 19:57
  • This one: https://blog.stephencleary.com/2013/11/there-is-no-thread.html – CodeMonkey May 19 '19 at 21:09
  • @YonatanNir Tasks are scheduled, indeed the waiting time does not tie up a thread, but once they have to do something, this action is scheduled on a thread. What thread picks it up depends, if there are many tasks they will be queued on a thread, but a free thread might still borrow tasks from another thread that has a long queue. If there are threads waiting on the thread pool, the thread pool will dispatch one of those. New threads are only created when the thread pool decides it needs one. – Dorus May 20 '19 at 05:21
  • Just to make sure, then you say that this blog entry is simply not true and a mistake? – CodeMonkey May 20 '19 at 11:22
  • @YonatanNir The blog is right. What the blog is talking about is is that while IO is happening, there is no thread actively polling that IO or in any other way tied up. Once the IO finishes, a low level hook will notify and eventually cause a thread to pick up handling the result. The thread that picks up the task might not be the one that cause the IO or `Task.Delay` to start, it might be another thread that came out of the thread pool. – Dorus May 20 '19 at 14:24
  • @Dorus I suggest you mean that this code will always print in the same schedule? I think it will and asking you this only because, I ran this code on online compiler and got different result and in Visual Studio Console app the same behaviour. – I.Step Mar 18 '20 at 13:06
  • @I.Step I think there is a chance the 5 second mark lines get reversed. Is that what you observed? – Dorus Mar 19 '20 at 17:10
  • @Dorus You can run it here: https://dotnetfiddle.net/. No it prints every time different results. My question is it normal or it should always print in schedule you wrote? (In normal c# application not online compiler) – I.Step Mar 19 '20 at 17:33
  • 1
    @I.Step Indeed, there is no locking here and both processes are scheduled separate, so the last 2 sets of lines can be reversed. If you look at the clock you see them happen very close to each-other. As i wrote above, accuracy is only good at 15-30ms, so anything scheduled closer than that has no guarantee about order. We're not working with a real time os here. – Dorus Mar 21 '20 at 21:02
  • When I wrote this: Task delay = Task.Delay(5000); await delay; It still waited for 5 seconds instead of not waiting. – Altaf Patel May 22 '20 at 16:42
41

I want to add something. Actually, Task.Delay is a timer based wait mechanism. If you look at the source you would find a reference to a Timer class which is responsible for the delay. On the other hand Thread.Sleep actually makes current thread to sleep, that way you are just blocking and wasting one thread. In async programming model you should always use Task.Delay() if you want something(continuation) happen after some delay.

crypted
  • 10,118
  • 3
  • 39
  • 52
  • 1
    'await Task.Delay()' frees the thread to do other things until the timer expires, 100% clear. But what if I cannot use 'await' since the method is not prefixed with 'async'? Then I can only call 'Task.Delay()'. In that case _the thread is still blocked_ but I have the _advantage of canceling the Delay()_. Is that correct? – Erik Stroeken May 04 '17 at 08:43
  • 9
    @ErikStroeken You can pass cancellation tokens to both thread and task. Task.Delay().Wait() will block, while Task.Delay() just creates the task if used without await. What you do with that task is up to you, but the thread continues. –  May 06 '17 at 04:38
33

if the current thread is killed and you use Thread.Sleep and it is executing then you might get a ThreadAbortException. With Task.Delay you can always provide a cancellation token and gracefully kill it. Thats one reason I would choose Task.Delay. see http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

I also agree efficiency is not paramount in this case.

Mr Balanikas
  • 1,657
  • 15
  • 28
  • 2
    Assume we got the following situation: `await Task.Delay(5000)`. When I kill the task I get `TaskCanceledException` (and suppress it) but my thread is still alive. Neat! :) – AlexMelw Jul 01 '17 at 10:11
  • A Thread that is sleeping can be woken with Thread.Interrupt(). This will cause sleep to throw an InterruptException. https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.interrupt?view=netcore-3.1 – Josh Jul 02 '20 at 21:33
10

Delayed would be a better name for Task.Delay - because it doesn't delay an existing task but rather creates a new 'delayed' one which on the other hand can be awaited and can cause suspension to the current task's body. It is essentially a Timer but without a callback/body.

Awaiting a delayed task creates a new item in async message queue and doesn't block any threads. The same thread where the await is called will proceed working on other tasks should there be any, and will return to the await point after the timeout (or when the preceding items in queue are complete). Tasks under the hood use Threads - there can be many Tasks scheduled and executed in a single thread. On the other hand if you happen to call Thread.Sleep() the thread will block, i.e. it will be out of play for the amount of time asked and won't process any async messages from the queue.

In .NET there're 2 major approaches to parallelism. The old one with Threads, ThreadPools etc. And the new one, based on Tasks, async/await, TPL. As a rule of thumb you don't mix APIs from these two universes.

pwrgreg007
  • 313
  • 3
  • 13
telepuz
  • 137
  • 1
  • 4
2

Yes, there are some general guidelines for when to use Task.Delay versus Thread.Sleep. Task.Delay is the preferred method for asynchronous programming in C#, because it allows you to wait asynchronously without blocking a thread. Thread.Sleep blocks the calling thread and can cause performance issues in applications with high concurrency.

There is no minimum value for Task.Delay to be effective or efficient over Thread.Sleep. In general, if you need to wait for a certain amount of time, it's best to use Task.Delay. If you need to block a thread for a specific amount of time, use Thread.Sleep.

As for the overhead of using Task.Delay in an async/await state machine, there is some overhead involved in context-switching. However, the overhead is generally small and is outweighed by the benefits of asynchronous programming. When using Task.Delay correctly, it can help improve the responsiveness and scalability of your application.

Ehsan Ehsani
  • 412
  • 3
  • 7
0

It is also worth to mention that Thread.Sleep(1) will fire GC faster.

This is purely based mine & team member observations. Lets assume that you have service which creates new task every for specific request (approx. 200-300 ongoing) and this task contains many weak references in flow. The task is working like state machine so we were firing the Thread.Sleep(1) on change state and by doing so we managed to optimize utilization of memory in the application - like I said before - this will makes GC to fire faster. It doesn't make so much difference in low memory consumption services (<1GB).

Jacek
  • 1
  • 1
  • 2
    Hi Jacek. Is this an experimental observation? If not, could you provide the source of this knowledge? – Theodor Zoulias Apr 16 '21 at 09:14
  • 1
    This is purely based mine & team member observations. Lets assume that you have service which creates new task every for specific request (approx. 200-300 ongoing) and this task contains many weak references in flow. The task is working like state machine so we were firing the Thread.Sleep(1) on change state and by doing so we managed to optimize utilization of memory in the application - like I said before - this will makes GC to fire faster. It doesn't make so much difference in low memory consumption services (<1GB). – Jacek Apr 21 '21 at 09:19
-1

I had a long argument with a colleague about this and he proved to me that there are significant differences beyond what the top answer currently shows. If you await Task.Delay(SomeMilliseconds) you can actually release callers other than your immediate parent on the stack:

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

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Started " + Thread.CurrentThread.ManagedThreadId);
            DoSomething1();
            Console.WriteLine("Finished " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(6000);
        }

        static async void DoSomething1()
        {
            Console.WriteLine("DoSomething1 Started " + Thread.CurrentThread.ManagedThreadId);
            var result = await DoSomething2();
            Console.WriteLine("DoSomething1 Finished " + Thread.CurrentThread.ManagedThreadId);
        }

        static async Task<int> DoSomething2()
        {
            Console.WriteLine("DoSomething2 Started " + Thread.CurrentThread.ManagedThreadId);

            await Task.Delay(5000);         // Will block DoSomething1 but release Main
            //Thread.Sleep(5000);           // Will block everything including Main
            //await Task.FromResult(5);     // Will return immediately (just for comparison)
            //await Task.Delay(0);          // What will it do, can you guess?

            Console.WriteLine("DoSomething2 Finished " + Thread.CurrentThread.ManagedThreadId);
            return 0;
        }
    }
}

Play with this code and observe the different effects of using Delay or using Sleep. The explanation is beyond the scope of this answer but can be summed up as "async functions don't start another thread until they await something that can't be immediately run (or the outcome determined)". This is the output:

Started 1
DoSomething1 Started 1
DoSomething2 Started 1
Finished 1
DoSomething2 Finished 4
DoSomething1 Finished 4

This isn't about DoSomething1(); in Main being fire and forget. You can prove that by using the Sleep. Also observe that when DoSomething2 "returns" from Task.Delay, it's running on a different thread.

This stuff is much smarter than I gave it credit for, believing that await just started a new thread to do stuff. I still don't pretend to understand it all, but the counter-intuitive result above shows there's much more going on under the hood than just starting threads to run code.

Derf Skren
  • 479
  • 2
  • 22
  • `async void DoSomething1()` <== [Avoid async void](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void). It is intended for event handlers, and the `DoSomething1` doesn't look like an event handler. – Theodor Zoulias Oct 27 '21 at 07:54
  • @TheodorZoulias yes, this is demonstration code intended to show what can happen if the base caller (or someone up the stack) is an event handler. You can also change that if you want to see the difference in behaviour. – Derf Skren Oct 29 '21 at 00:24
  • 1
    Derf Skren if you have to use bad practices for some reason, you should explain what the reason is inside the answer, and also warn the readers about the dangers of the specific bad practice. Otherwise you are learning to the people habits that will later have to unlearn. – Theodor Zoulias Oct 29 '21 at 06:45
-2

My opinion,

Task.Delay() is asynchronous. It doesn't block the current thread. You can still do other operations within current thread. It returns a Task return type (Thread.Sleep() doesn't return anything ). You can check if this task is completed(use Task.IsCompleted property) later after another time-consuming process.

Thread.Sleep() doesn't have a return type. It's synchronous. In the thread, you can't really do anything other than waiting for the delay to finish.

As for real-life usage, I have been programming for 15 years. I have never used Thread.Sleep() in production code. I couldn't find any use case for it. Maybe that's because I mostly do web application development.

Ike
  • 1,194
  • 12
  • 18
  • 1
    Note: if you write "await Task.Delay()", it becomes synchronous again. - I don't think that statement is correct. Surely it asynchronous because the thread is free to carry on running the caller code and at some point in the future a thread will pick up this work after the delay has finished – Josh Jul 02 '20 at 21:30
-2

In an async program, the difference between

await task.Delay() 
//and 
thread.sleep 

is nominal in a simple app, one might be more cancellable, one might be more accurate, one might be a tiny bit faster... but at the end of the day, both do the same thing, they block the executing code...

Here are the results:

1 00:00:00.0000767
Not Delayed.
1 00:00:00.2988809
Delayed 1 second.
4 00:00:01.3392148
Delayed 3 second.
5 00:00:03.3716776
Delayed 9 seconds.
5 00:00:09.3838139
Delayed 10 seconds
4 00:00:10.3411050
4 00:00:10.5313519

From this code:

var sw = new Stopwatch();
sw.Start();
Console.WriteLine($"{sw.Elapsed}");
var asyncTests = new AsyncTests();

var go1 = asyncTests.WriteWithSleep();
var go2 = asyncTests.WriteWithoutSleep();

await go1;
await go2;
sw.Stop();
Console.WriteLine($"{sw.Elapsed}");
        
Stopwatch sw1 = new Stopwatch();
Stopwatch sw = new Stopwatch();
    public async Task WriteWithSleep()
    {
        sw.Start();
        var delayedTask =  Task.Delay(1000);
        Console.WriteLine("Not Delayed.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        await delayedTask;
        Console.WriteLine("Delayed 1 second.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        Thread.Sleep(9000);
        Console.WriteLine("Delayed 10 seconds");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        sw.Stop();
    }
    public async Task WriteWithoutSleep()
    {
        await Task.Delay(3000);
        Console.WriteLine("Delayed 3 second.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
        await Task.Delay(6000);
        Console.WriteLine("Delayed 9 seconds.");
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} {sw.Elapsed}");
    }

Sleep acts the same way as an immediate await, except it blocks the thread. A task that is assigned to a var may cause a thread switch when it is finally awaited. In this example, it looks like the code starts on thread 1, then creates thread 5 for WriteWithoutSleep(), but continues executing on thread 1 for ThreadWithSleep() until delayedTask is awaited. At that moment, thread 1's code flows into thread 4 and further execution in Main is now on thread 4; thread 1 is for lack of a better word thrown away.

All of the above answers are very valuable. However, In a simple console app, it doesn't seem to matter except maybe academically over the course of several runs which you use if you immediately await your Task.Delay() and don't intend to use cancellation tokens;

In a complex app, putting threads to sleep vs. jumping from thread to thread due to creating tasks and awaiting them later vs. immediate awaits could be points of consideration.

Finally, putting a Process.GetCurrentProcess().Threads.Count at the beginning of a console app (at least mine) produced 13 threads in debugger mode. After the awaited calls, I had 17 threads in debugger mode in visual studio. I've read that ConsoleApps have only 3 threads and the rest are debugger threads, but running the consoleApp without debugging in visual studio resulted in 8 and then 14 threads. Running it outside visual studio resulted in 8 then 14 threads.

Copying the code and pasting it just afterwards had the same number of the threads, 8,14 and everything stayed on threads 4 and 5. The second thread.sleep and task.delays did not cause thread jumps. And all of this research is to propose: while thread.sleep will block a thread and task.delay will not and has a cancellation token, unless your app is pretty complex, it really doesn't matter as on the surface: task.delay and thread.sleep do pretty much the same thing.

Patrick Knott
  • 1,666
  • 15
  • 15
  • 2
    *"both do the same thing, they block the executing thread..."* -- AFAIK the `await task.Delay()` doesn't block any thread, and if you want I can easily prove it. – Theodor Zoulias May 10 '22 at 16:11
  • @TheodorZoulias please do prove that the thread is blocked. Many people say that async doesn't spin up new threads of code. This uses thread.sleep, so allegedly, both async methods would be affected by a thread.sleep on one of the async methods, but they are not affected, as shown in the results. The critical point is thread.sleep does block the async task, but so does the await task.delay(). – Patrick Knott May 10 '22 at 17:42
  • 3
    Patrick [here](https://dotnetfiddle.net/nXlVmJ) is a proof that the `await task.Delay()` **doesn't** block a thread. I create 100,000 tasks that are awaiting a `await task.Delay(1000)`, and then wait them all to complete. They all complete after 1000 msec. If each `await task.Delay()` was blocking a thread, I would need 100,000 threads. Each thread requires [1 MB](https://stackoverflow.com/questions/28656872/why-is-stack-size-in-c-sharp-exactly-1-mb) of RAM, so I would need 100 GB RAM. My machine has only 4GB RAM. So it's impossible that my app created so many threads. – Theodor Zoulias May 10 '22 at 18:27
  • @TheodorZoulias thank you for that information, but that was not what I was asking nor pointing out. I was pointing out that async does not spin up new threads allegedly. When two async functions are called simultaneously, and one has a thread.sleep and the other has a task.Delay() there is no difference between the two. Both will continue to execute. The Thread.Sleep seems to function no differently than the task.Delay() inside an async method. If it is true that async does not spin up new threads, then these two function the same. – Patrick Knott May 12 '22 at 19:21
  • https://stackoverflow.com/questions/27265818/does-the-use-of-async-await-create-a-new-thread#:~:text=The%20async%20and%20await%20keywords,when%20the%20method%20is%20active. Since async doesn't create a new thread, and the execution of the second sibling continues to occur even though the thread is put to sleep in the first sibling, and the parent continues to execute until an await is called, these two things function the same. – Patrick Knott May 12 '22 at 19:30
  • I am trying to say: task.Delay() == thread.Sleep() (not === due to cancellation) in async methods Because async does not spin up new threads and thread.Sleep() does not put the parent method or the sibling methods to sleep that are executing on the same thread. Execution of all code continues, including timers, etc. etc. and siblings continue to execute despite a call to thread.Sleep() in another sibling. The parent method continues to execute as well until an await is called. – Patrick Knott May 12 '22 at 19:42
  • 2
    Patrick [your answer](https://stackoverflow.com/revisions/72189598/1) contains this text: *"both do the same thing, they block the executing thread..."*. How many people who will read this sentence, and will understand that you are not talking literally about blocked threads, but instead you are talking about suspended execution flows? Not very many. Getting the terminology correctly matters, otherwise people might learn from your answer things that will later have to unlearn. – Theodor Zoulias May 12 '22 at 20:00
  • 1
    @TheodorZoulias thank you for the correction: I have updated it to read: both do the same thing, they block the executing code... – Patrick Knott May 12 '22 at 20:05
  • 1
    *"A task that is assigned to a var causes a thread switch when it is finally awaited."* -- This is not always true. For example in a WinForms application if you `await Task.Delay(1000)` in the async handler of a `Click` event, the same thread will run the code after the `await`. That's because there is a special `SynchronizationContext` installed on the UI thread of all WinForms applications. – Theodor Zoulias May 19 '22 at 23:45