一、理论分析
回答这个要先从线程时间精度(时间片)开始说起。很多参考书说,默认情况下,时间片为15ms 左右,但是这是已经过时的知识。在老的 Windows 操作系统里,应用程序模式时时间片 15ms 左右,准确一点是15.625 左右。服务器模式是其双倍。但是在微软推行.Net 普及 Task之后,规则已经改动,并且做了精细的调节。现在Win10的时间精度为1ms(不用担心谷歌浏览器修改时间精度了),Win2013服务器的时间精度为7ms,使用微软提供的测试工具可以测出来:ClockRes - Sysinternals | Microsoft Learn
命令行:powercfg /energy
我们可以通过它可以更直观的看出:
因为有这个请求在,所以无论如何最低的计时器分辨率都只能为1ms。
所以,现在Win10 默认状态下,Sleep(1) 占时不再是15ms,而是1ms 左右:
1120us 即 1.12ms
下面现在正式回答,Thread.Sleep(0),Thread.Yield(),Thread.Sleep(1) 有什么不同:首先,Thread.Sleep 只是放弃时间片的剩余时间,让系统重新选择调度一个合适的线程(其优先级等于或者高于当前线程)。在没有其他活动线程的情况下,使用Sleep(0) 还是选上原来线程,即连任,如果连任了,系统不会对其做上下文切换,所以有:
由上面的图片可知耗时为18us左右,即0.018ms
其次,Thread.Yield() 是什么呢?跟 Thread.Sleep 有点像,但是它跟 Thread.Sleep(0) 有点区别:系统选择继任时可以选择优先级比原来低的线程。最后,Thread.Sleep(1) 是什么情况呢?前面说了,Thread.Sleep(0) 只是放弃时间片的剩余时间,让系统重新选择调度一个合适的线程。但是 Thread.Sleep(1) 却让当前线程沉睡了(即使只有1ms )。也就是说,主动放弃下次竞选,所以不可能连任,系统上下文必然发生切换(优先级低于原线程的家伙也有机会)。
二、最后总结一下Sleep(0)的用途
1)在图形界面程序中,使用Thread.sleep(0)可以避免长时间运行的任务阻塞UI线程的执行。例如在GUI程序中,当用户通过按钮点击或其他事件触发某个任务时,在该任务完成前可能需要等待某个数据加载、文件下载或其他操作完成。如果不使用Thread.sleep(0),就可能导致主线程阻塞而导致程序无响应。
2)在多线程爬虫程序中,使用Thread.sleep(0)可以有效地限制连接网站的频率,避免过于频繁访问同一目标网站而被封禁IP。例如,在高并发情况下,为了提高效率,某些爬虫程序会采用多线程方式进行网站抓取,这样往往需要控制每个线程之间的请求频率,以避免对目标网站造成负担或被视作恶意攻击。
3)在多线程程序中,使用Thread.sleep(0)可以优化线程调度算法,使得程序更加平滑和高效。例如在Java Web应用中,Tomcat服务器等Web容器采用线程池来管理客户端请求。为了避免某些线程占用关键资源导致其他线程无法响应,可以使用Thread.sleep(0)控制每个线程之间的竞争性,使得整个程序稳定、流畅地运行。
三、效率方面影响
Thread.Sleep(N)会严重影响当前线程中循环的运行效率,原因上面已经说明的很清楚了,上下文切换+线程竞争,所以在高速定时器的应用中:
1、Timer的Period小于10ms,精度会很差,Timer不能满足精度要求,在高速定时器中不推荐用Timer
Timer timer = new Timer(TimerCallback, null, 0, 2);
timer.Change(0, 2);
long times = 0;
void TimerCallback(object sender)
{
times++;
Thread.Sleep(1);
}
运行一分钟后的结果:3767,远低于1000/2*60=30000
2、用for循环实现Timer的功能,不管是Task.Delay还是Thread.Sleep都实现不了小于10ms(>100HZ的精度)
1) 使用 Task.Delay 和 async/await:对于10ms以下精度不够
Delay(TimeSpan.FromMilliseconds(2), null);
public async Task Delay(TimeSpan interval, Func<Task> func)
{
while (true)
{
await Task.Delay(interval);
times++;
}
}
运行一分钟后的结果:3729,远低于1000/2*60=30000
2)使用 Thread.Sleep:精度稍微好些,但是不够
DelaySleep();
public async Task DelaySleep()
{
Task.Run(() =>
{
while (true)
{
Thread.Sleep(2);
times++;
}
});
}
运行一分钟后的结果:19599,还是低于1000/2*60=30000
3、解决思路:Timer大时间里面写循环
DelayLoop(TimeSpan.FromMilliseconds(1000), func);
public async Task DelayLoop(TimeSpan interval, Action func)
{
while (true)
{
await Task.Delay(interval);
func();
}
}
private void func()
{
for(int imm = 0; imm<500; imm++)
{
Thread.Sleep(0);
lock (this)
{
times++;
}
}
}
运行一分钟后的结果:29000,稍微低于1000/2*60=30000,由于是手动一分钟后点击,没有写成一分钟自动停止,所以存在误差,有兴趣的同学可以测试下,可以满足高速定时器功能,已在项目中使用
参考资料:
1、(43 封私信) C# Thread.Sleep(0)有什么用? - 知乎 (zhihu.com)
2、https://blog.csdn.net/weixin_73077810/article/details/130519033
--以上。