Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is going on with Task.Delay().Wait()?

I'm confused why Task.Delay().Wait() takes 4x more time, then Thread.Sleep()?

E.g. task-00 was running on only thread 9 and took 2193ms? I'm aware, that sync wait is bad in tasks, because the whole thread being blocked. It is just for test.

Simple test in console application:

bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(() =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");                     
            //Thread.Sleep(wait);
            Task.Delay(wait).Wait();
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
            ;
        });
    }
}
Console.ReadKey();
return;

With Task.Delay().Wait():
task-03 ThrID: 05, Wait=300ms, START: 184ms
task-04 ThrID: 07, Wait=100ms, START: 184ms
task-00 ThrID: 09, Wait=100ms, START: 0ms
task-06 ThrID: 04, Wait=100ms, START: 185ms
task-01 ThrID: 08, Wait=300ms, START: 183ms
task-05 ThrID: 03, Wait=300ms, START: 185ms
task-02 ThrID: 06, Wait=100ms, START: 184ms
task-07 ThrID: 10, Wait=300ms, START: 209ms
task-07 ThrID: 10, Wait=300ms, END: 1189ms
task-08 ThrID: 12, Wait=100ms, START: 226ms
task-09 ThrID: 10, Wait=300ms, START: 226ms
task-09 ThrID: 10, Wait=300ms, END: 2192ms
task-06 ThrID: 04, Wait=100ms, END: 2193ms
task-08 ThrID: 12, Wait=100ms, END: 2194ms
task-05 ThrID: 03, Wait=300ms, END: 2193ms
task-03 ThrID: 05, Wait=300ms, END: 2193ms
task-00 ThrID: 09, Wait=100ms, END: 2193ms
task-02 ThrID: 06, Wait=100ms, END: 2193ms
task-04 ThrID: 07, Wait=100ms, END: 2193ms
task-01 ThrID: 08, Wait=300ms, END: 2193ms

With Thread.Sleep():
task-00 ThrID: 03, Wait=100ms, START: 0ms
task-03 ThrID: 09, Wait=300ms, START: 179ms
task-02 ThrID: 06, Wait=100ms, START: 178ms
task-04 ThrID: 08, Wait=100ms, START: 179ms
task-05 ThrID: 04, Wait=300ms, START: 179ms
task-06 ThrID: 07, Wait=100ms, START: 184ms
task-01 ThrID: 05, Wait=300ms, START: 178ms
task-07 ThrID: 10, Wait=300ms, START: 184ms
task-00 ThrID: 03, Wait=100ms, END: 284ms
task-08 ThrID: 03, Wait=100ms, START: 184ms
task-02 ThrID: 06, Wait=100ms, END: 285ms
task-09 ThrID: 06, Wait=300ms, START: 184ms
task-04 ThrID: 08, Wait=100ms, END: 286ms
task-06 ThrID: 07, Wait=100ms, END: 293ms
task-08 ThrID: 03, Wait=100ms, END: 385ms
task-03 ThrID: 09, Wait=300ms, END: 485ms
task-05 ThrID: 04, Wait=300ms, END: 486ms
task-01 ThrID: 05, Wait=300ms, END: 493ms
task-07 ThrID: 10, Wait=300ms, END: 494ms
task-09 ThrID: 06, Wait=300ms, END: 586ms

Edit:
With async lambda and await Task.Delay() is as fast as Thread.Sleep(), may be also faster (511ms).
Edit 2:
With ThreadPool.SetMinThreads(16, 16); Task.Delay().Wait() works as fast as Thread.Sleep for 10 iteration in the loop. With more iterations it's slower again. It's also interesting, that if without adjusting I increase the number of iterations for Thread.Sleep to 30, it's still faster, then 10 iteration with Task.Delay().Wait()
Edit 3:
The overloading Task.Delay(wait).Wait(wait) works as fast as Thread.Sleep()

like image 549
Rekshino Avatar asked Dec 06 '18 09:12

Rekshino


People also ask

Does task delay create a new thread?

Task. Delay does not create new Thread, but still may be heavy, and no guaranties on order of execution or being precise about deadlines.

Does task delay block thread?

Delay(1000) doesn't block the thread, unlike Task. Delay(1000).

What is task delay?

The Delay method is typically used to delay the operation of all or part of a task for a specified time interval. Most commonly, the time delay is introduced: At the beginning of the task, as the following example shows.

How does task Wait work?

Wait is a synchronization method that causes the calling thread to wait until the current task has completed. If the current task has not started execution, the Wait method attempts to remove the task from the scheduler and execute it inline on the current thread.


2 Answers

Neither Thread.Sleep(), nor Task.Delay() guarantee that the internal will be correct.

Thread.Sleep() and Task.Delay() work very differently. Thread.Sleep() blocks the current thread and prevents it from executing any code. Task.Delay() creates a timer that will tick when the time expires and assigns it to execution on the threadpool.

You run your code by using Task.Run(), which will create tasks and enqueue them on the threadpool. When you use Task.Delay(), the current thread is released back on the thread pool, and it can start processing another task. In this way, multiple tasks will start faster and you will record startup times for all. Then, when the delay timers start ticking, they also exhaust the pool, and some tasks take quite longer to finish than since they started. That is why you record long times.

When you use Thread.Sleep(), you block the current thread on the pool and it is unable to process more tasks. The Thread pool doesn't grow immediately, so new tasks just wait. Therefore, all tasks run at about the same time, which seem faster to you.

EDIT: You use Task.Wait(). In your case, Task.Wait() tries to inline the execution on the same thread. At the same time, Task.Delay() relies on a timer that gets executed on the thread pool. Once by calling Task.Wait() you block a worker thread from the pool, second you require an available thread on the pool to complete the operation of the same worker method. When you await the Delay(), no such inlining is required, and the worker thread is immediately available to process timer events. When you Thread.Sleep, you don't have a timer to complete the worker method.

I believe this is what causes the drastic difference in the delay.

like image 157
Nick Avatar answered Sep 29 '22 03:09

Nick


Your problem is that you are mixing asynchronous code with synchronous code without using async and await. Don't use synchronous call .Wait, it's blocking your thread and that's why asynchronous code Task.Delay() won't work properly.

Asynchronous code often won't work properly when called synchronously because it isn't designed to work that way. You can get lucky and asynchronous code seems to work when running synchronously. But if you are using some external library author of that library can change their code in a way to will break your code. Asynchronous code should be all the way down asynchronous.

Asynchronous code is usually slower than synchronous one. But benefit is that it runs asynchronously, example if your code is waiting for file to load some other code can run on same CPU Core while that file is loading.

Your code should look like below, but with async you can't be sure that ManagedThreadId will stay the same. Because thread running your code can change during execution. You should never use ManagedThreadId property or [ThreadStatic] attribute if you using asynchronous code anyway because of that reason.

Async/Await - Best Practices in Asynchronous Programming

bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(async () =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");
            await Task.Delay(wait);
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
        });
    }
}
Console.ReadKey();
return;
like image 25
Wanton Avatar answered Sep 29 '22 04:09

Wanton