Using waiting for Task.Delay in performance killing

Let's say I want to start about N jobs per second, equally distributed.

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 start in 1 second, but for numberOfCallsPerSecond = 100

it it takes 16 seconds

on my 12 core CPU. It seems like waiting for Task.Delay adds a lot of overhead (of course, without it in place, call generation happens in 3ms.

I didn't expect waiting to add so much overhead in this scenario. This is normal?

EDIT:

Please forget about calling (). Running this code shows a similar 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 about 10 seconds, I expected it to Generate

take about 1 second and not 10 times as long

+3


source to share


3 answers


Task.Delay

is easy but not accurate. Since the no-delay loop completes much faster, it sounds like your thread is not running and is using OS sleep to wait for the timer to expire. The timer is checked according to the OS thread scheduling quantization (in the same interrupt handler that performs thread preemption), which is 16ms by default.

You can reduce the quantum with timeBeginPeriod

, but the best (more energy efficient) approach if you need to limit the speed rather than the exact time is to keep track of the elapsed time (the class Stopwatch

is good for this) and the number of calls made and only the delay when the calls made got to past tense. The overall effect is that your thread wakes up ~ 60 times per second and starts multiple work items every time it does. If your processor gets busy with something else, you will trigger additional work items when you take control back - although it's also fairly simple to limit the number of items that can run immediately if you need to.



public async Task Generate(int numberOfCallsPerSecond) 
{
    var elapsed = Stopwatch.StartNew();
    var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000 / numberOfCallsPerSecond miliseconds
    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);
    }
}

      

+12


source


My psychic debugger says that your method Call

has a significant synchronous part (i.e. the before part await

) that takes time to execute synchronously.

If you want the method to Generate

only "run" these calls Call

and run them at the same time (including the synchronous parts), you must offload them to the thread ThreadPool

using Task.Run

:

var task = Task.Run(() => Call());
await Task.Delay(delay);

      




Task.Delay

adds almost no overhead. It uses an internal System.Threading.Timer

one that requires very few resources.

+1


source


If you use a timeslot with Task.Delay () it will kill the CPU. Use an integer and it won't. True story. I do not know why.

0


source







All Articles