C# Thread.Sleep(0)有什么用?

一、理论分析

回答这个要先从线程时间精度(时间片)开始说起。很多参考书说,默认情况下,时间片为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以下精度不够
cs 复制代码
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:精度稍微好些,但是不够

cs 复制代码
DelaySleep();

public async Task DelaySleep()
{
    Task.Run(() =>
    {
        while (true)
        {
            Thread.Sleep(2);
            times++;
        }
    });
}

运行一分钟后的结果:19599,还是低于1000/2*60=30000

3、解决思路:Timer大时间里面写循环

cs 复制代码
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

--以上。

相关推荐
Hellc0072 小时前
什么是 WebApiEngine?
c#
dangoxiba2 小时前
【Unity学习心得】如何使用Unity制作“饥荒”风格的俯视角2.5D游戏
游戏·unity·c#·游戏引擎
咩咩觉主2 小时前
en造数据结构与算法C# 群组行为优化 和 头鸟控制
开发语言·c#
一丝晨光3 小时前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
friklogff4 小时前
【C#生态园】从图像到视觉:Emgu.CV、AForge.NET、OpenCvSharp 全面解析
开发语言·c#·.net
friklogff6 小时前
【C#生态园】构建你的C#操作系统:框架选择与实践
服务器·开发语言·c#
code bean8 小时前
【C#基础】函数传参大总结
服务器·开发语言·c#
shanshan20999 小时前
上位机系统架构 | 如何设计一个高效的多相机管理系统
c#·wpf·相机
ling1s11 小时前
C#基础(13)结构体
前端·c#
.Net Core 爱好者11 小时前
Redis实践之缓存:设置缓存过期策略
java·redis·缓存·c#·.net