使用await Task.Delay进行杀戮性能

Dan*_*inu 5 .net c# asynchronous async-await

假设我想开始大致每秒分配N个任务.

所以我尝试了这个:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

起初我预计这将在1秒内运行,但是numberOfCallsPerSecond = 100它需要16 seconds我的12核CPU.似乎等待Task.Delay增加了很多开销(当然没有它就可以在3ms内生成调用.

我没想到await会在这种情况下增加很多开销.这是正常的吗?

编辑:

请忘记Call().运行此代码显示类似的结果:

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);
 }
}
Run Code Online (Sandbox Code Playgroud)

我试着用numberOfCallsPerSecond = 500它运行它需要大约10秒,我预计Generate大约需要1秒,而不是10倍

Ben*_*igt 16

Task.Delay重量轻但不准确.由于没有延迟的循环完成得更快,听起来你的线程正在空闲并使用操作系统休眠等待计时器过去.根据OS线程调度量程(在执行线程抢占的同一中断处理程序中)检查定时器,默认为16ms.

你可以减少量子timeBeginPeriod,但是如果你需要速率限制而不是精确的定时更好(更节能)的方法是跟踪经过的时间(Stopwatch类是好的)和调用的数量,并且只在拨打电话已经赶上了经过的时间.总体效果是你的线程每秒会被唤醒~60次,并且每次都会启动一些工作项.如果您的CPU忙于其他事情,那么当您获得控制权时,您将启动额外的工作项目 - 尽管如果这是您需要的,那么同时启动项目的数量也非常简单.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • `await Task.Delay()`不会导致线程在给定时间内休眠.相反,它使用计时器并在计时器过去时安排继续执行.所以重要的是计时器的分辨率,而不是睡眠的分辨率.虽然默认情况下两者都是16毫秒,这就是为什么观察到的行为与您描述的行为相同. (2认同)