Search code examples
c#asynchronousasync-awaitwait

Are there performance differences between await Task.Delay() and Task.Delay().Wait() in a separate Task?


I have a construct like this:

Func<object> func = () =>
{
    foreach(var x in y)
    {
        ...
        Task.Delay(100).Wait();
    }
    return null;
}
var t = new Task<object>(func);
t.Start(); // t is never awaited or waited for, the main thread does not care when it's done.
...

So basically i create a function func that calls Task.Delay(100).Wait() quite a few times. I know the use of .Wait() is discouraged in general. But I want to know if there are concrete performance losses for the displayed case.

The .Wait() calls happen in a separate Task, that is completely independend from the main Thread, i.e. it is never awaited or waited for. I am curious what happens when I call Wait() this way, and what happens on the processor of my machine. During the 100 ms that are waited, can the processor core execute another thread, and returns to the Task after the time has passed? Or did I just produce a busy waiting procedure, where my CPU "actively does nothing" for 100 ms, thus slowing down the rest of my program?

Does this approach have any practical downsides when compared to a solution where i make it an async function and call await Task.Delay(100)? That would be an option for me, but I would rather not go for it if it is reasonably avoidable.


Solution

  • There are concrete efficiency losses because of the inefficient use of threads. Each thread requires at least 1 MB for its stack, so the more threads that are created in order to do nothing, the more memory is allocated for unproductive purposes.

    It is also possible for concrete performance losses to appear, in case the demand for threads surpasses the ThreadPool availability. In this case the ThreadPool becomes saturated, and new threads are injected in the pool in a conservative (slow) rate. So the tasks you create and Start will not start immediately, but instead they will be entered in an internal queue, waiting for a free thread, either one that completed some previous work, or an new injected one.

    Regarding your concerns about creating busy waiting procedures, no, that's not what happening. A sleeping thread does not consume CPU resources.

    As a side note, creating cold Tasks using the Task constructor is an advanced technique that's only used in special occasions. The common way of creating delegate-based tasks is through the convenient Task.Run method, that returns hot (already started) tasks.