使用await Task.Delay进行杀戮性能
假设我想开始大致每秒分配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会增加很多开销。 这是正常的吗?
编辑:
请忘记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); } }
我尝试使用numberOfCallsPerSecond = 500
来运行它,它需要大约10秒,我预计Generate
需要大约1秒,而不是10倍以上
Task.Delay
很轻但不准确。 由于没有延迟的循环完成得更快,听起来你的线程正在空闲并使用操作系统hibernate等待计时器过去。 根据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); } }
我的通灵调试器说你的Call
方法有一个重要的同步部分(即await
之前的部分)需要时间来同步执行。
如果您希望Generate
方法仅“ Task.Run
”这些Call
调用并让它们并发运行(包括同步部分),则需要使用Task.Run
将它们卸载到ThreadPool
线程:
var task = Task.Run(() => Call()); await Task.Delay(delay);
Task.Delay
几乎没有增加任何开销。 它在内部使用System.Threading.Timer
,只需要很少的资源。
如果你使用Task.Delay()的时间跨度,它将杀死CPU。 使用整数,它不会。 真实的故事。 不明白为什么。