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

时间:2014-11-03 14:12:58

标签: c# .net 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);
    }
}

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

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

编辑:

请忘记电话()。运行此代码显示类似的结果:

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);
 }
}

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

3 个答案:

答案 0 :(得分:14)

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);
    }
}

答案 1 :(得分:1)

我的通灵调试器说你的Call方法有一个重要的同步部分(即await之前的部分)需要时间同步执行。

如果您希望Generate方法仅“激活”这些Call调用并让它们同时运行(包括同步部分),则需要将它们卸载到ThreadPool个线程使用Task.Run

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

Task.Delay几乎没有任何开销。它在内部使用System.Threading.Timer,只需要很少的资源。

答案 2 :(得分:-1)

如果你使用Task.Delay()的时间跨度,它将杀死CPU。使用整数,它不会。真实的故事。不明白为什么。