Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using await Task.Delay in a for kills performance

Let's say I want to start roughly N tasks per second distributed equally.

So I tried this:

public async Task Generate(int numberOfCallsPerSecond) 
{
    var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000 / numberOfCallsPerSecond miliseconds
    for (int i=0; i < numberOfcallsPerSecond; i++) 
    {
        Task t = Call();  // don't wait for result here
        await Task.Delay(delay);
    }
}

At first I expected this to run in 1 second but for numberOfCallsPerSecond = 100 it takes 16 seconds on my 12 core CPU. It seems the await Task.Delay adds a lot of overhead (of course without it in place generation of the calls happens in 3ms.

I didn't expect that await would add so much overhead in this scenario. Is this normal?

EDIT:

Please forget about the Call(). Running this code shows similiar result:

public async Task Generate(int numberOfCallsPerSecond) 
{
var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000 / numberOfCallsPerSecond miliseconds
for (int i=0; i < numberOfcallsPerSecond; i++) 
{
    await Task.Delay(delay);
 }
}

I tried to run it with numberOfCallsPerSecond = 500 and it takes around 10 seconds, I expected Generate to take roughly 1 second, not 10 times more

like image 424
Dan Dinu Avatar asked Nov 03 '14 14:11

Dan Dinu


People also ask

Is task delay better than thread sleep?

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.

Does async await improve performance?

The main benefits of asynchronous programming using async / await include the following: Increase the performance and responsiveness of your application, particularly when you have long-running operations that do not require to block the execution.

What happens if you do not await a task?

If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.

Does await task delay block thread?

await Task. Delay(1000) doesn't block the thread, unlike Task. Delay(1000). Wait() would do, more details.


1 Answers

Task.Delay is lightweight but not accurate. Since the loop without delay completes much faster, it sounds like your thread is going idle and using an OS sleep to wait for the timer to elapse. The timer is checked according to the OS thread scheduling quantum (in the same interrupt handler which performs thread pre-emption), which is 16ms by default.

You can reduce the quantum with timeBeginPeriod, but a better (more power efficient) approach if you need rate limiting rather than exact timing is to keep track of elapsed time (the Stopwatch class is good for this) and number of calls made, and only delay when calls made have caught up to elapsed time. The overall effect is that your thread will wake up ~60 times per second, and start a few work items each time it does. If your CPU becomes busy with something else, you'll start extra work items when you get control back -- although it's also pretty straightforward to cap the number of items started at once, if that's what you need.

public async Task Generate(int numberOfCallsPerSecond) 
{
    var elapsed = Stopwatch.StartNew();
    var delay = TimeSpan.FromMilliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000 / numberOfCallsPerSecond milliseconds
    for (int i=0; i < numberOfcallsPerSecond; i++) 
    {
        Call();  // don't wait for result here
        int expectedI = elapsed.Elapsed.TotalSeconds * numberOfCallsPerSecond;
        if (i > expectedI) await Task.Delay(delay);
    }
}
like image 179
Ben Voigt Avatar answered Nov 09 '22 23:11

Ben Voigt