千家信息网

.net framework如何实现高精度定时器

发表于:2024-11-24 作者:千家信息网编辑
千家信息网最后更新 2024年11月24日,这篇文章主要介绍了.net framework如何实现高精度定时器,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1背景.NET Fra
千家信息网最后更新 2024年11月24日.net framework如何实现高精度定时器

这篇文章主要介绍了.net framework如何实现高精度定时器,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

1背景

.NET Framework 提供了四种定时器,然而其精度都不高(一般情况下 15ms 左右),难以满足一些场景下的需求。

在进行媒体播放、绘制动画、性能分析以及和硬件交互时,可能需要 10ms 以下精度的定时器。这里不讨论这种需求是否合理,它是确实存在的问题,也有相当多的地方在讨论,说明这是一个切实的需求。然而,实现它并不是一件轻松的事情。

这里并不涉及内核驱动层面的定时器,只分析在 .NET 托管环境下应用层面的高精度定时器实现。

Windows 不是实时操作系统,所以任何方案都无法绝对保证定时器的精度,只是能尽量减少误差。所以,系统的稳定性不能完全依赖于定时器,必须考虑失去同步时的处理。

2等待策略

想要实现高精度定时器,必然需要等待和计时两种基础功能。等待用来跳过一定时间间隔,计时可以进行时间检查,用以调整等待时间。

等待策略实际就是两种:

  • 自旋等待:让 CPU 空转消耗时间,占用大量 CPU 时间,但是时间高度可控。

  • 阻塞等待:线程进入阻塞状态,出让 CPU 时间片,在等待一定时间后再由操作系统调度回到运行状态。阻塞时不占用 CPU,然而需要操作系统调度,时间难以控制。

可以看到二者各有优劣,应该按照不同需求进行不同的实现。

而计时机制可以说能用的只有一种,就是Stopwatch类。它内部使用了系统 API QueryPerformanceCounter/ QueryPerformanceFrequency来进行高精度计时,依赖于硬件,它的精度可以高达几十纳秒,非常适合用来实现高精度定时器。

所以难点在于等待策略,下面先分析简单的自旋等待。

2.1自旋等待

可以使用Thread.SpinWait(int iteration)来进行自旋,也就是让 CPU 在一个循环里空转,iteration参数是迭代次数。.NET Framework 中不少同步构造都用到了它,用来等待一小段时间,减少上下文切换的开销。

这里很难根据iteration来计算消耗的时间,因为 CPU 速度可能是动态的。所以需要结合使用Stopwatch。伪代码如下:

var 等待开始时间 = 当前计时;while ((当前计时 - 等待开始时间) < 需要等待的时间){自旋;}

写成实际代码:

void Spin(Stopwatch w, int duration){var current = w.ElapsedMilliseconds;while ((w.ElapsedMilliseconds - current) < duration)Thread.SpinWait(10);}

这里的w是一个已经启动的Stopwatch,为了演示简单使用了ElapsedMilliseconds属性,精度是毫秒级的,使用ElapsedTicks属性就可以获得更高的精度(微秒级)。

然而如前所述,这样精度高但是是以消耗 CPU 时间为代价的,这样实现定时器会让一个 CPU 核心满负荷工作(如果执行的任务也没有阻塞的话)。相当于浪费了一个核心,在有些时候不太现实(比如核心很少甚至是单核的虚拟机上),所以需要考虑阻塞等待。

2.2阻塞等待

阻塞等待会把控制权交给操作系统,这样就必须确保操作系统能够及时的将定时器线程调度回运行状态。默认情况下,Windows 的系统定时器精度是 15.625ms,也就是说时间切片是这个尺寸。如果线程阻塞,出让其时间片进行等待,再被调度运行的时间至少是一个切片 15.625ms。那么必须减少时间切片的长度,才有可能实现更高的精度。

可以通过系统 API timeBeginPeriod来修改系统定时器精度到 1ms(它内部使用了没有给出文档的NtSetTimerResolution,这个 API 可以修改到 0.5ms)。不需要的时候使用timeEndPeriod还原。

修改系统定时器精度有副作用。它会增加上下文切换的开销,增加耗电量,降低系统整体性能。然而,很多程序都不得不这么做,因为没有其它方式能获得更高的定时器精度。比如基于 WPF 的程序(包括 Visual Studio)、使用 Chromium 内核的应用(Chrome、QQ)、多媒体播放器、游戏等等很多程序都会在一定时间内把系统定时器精度修改到 1ms。(查看方法见后面)

所以实际上这个副作用在桌面环境已经成为常态。而且从 Windows 8 开始,这个副作用减弱了。

在 1ms 的系统定时器精度前提下,可以使用三种方式实现阻塞等待:

  • Thread.Sleep

  • WaitHandle.WaitOne

  • Socket.Poll

另外,多媒体定时器timeSetEvent也使用了阻塞的方式。

Thread.Sleep

它的参数使用毫秒单位,所以最多只能精确到 1ms。不过事实上很不稳定,Thread.Sleep(1)会在 1ms 与 2ms 两种状态间跳动,也就是可能会产生 +1ms 多的误差。

实测发现,没有任务负载的情况下(纯粹循环调用Sleep(1)),阻塞时长稳定在 2ms;而有任务负载时,则至少会阻塞 1ms。这和其它两种阻塞方式不同,详见后文。

如果需要修正这个误差,可以在阻塞 n 毫秒时,使用Sleep(n-1),并通过Stopwatch计时,剩余等待时间用Sleep(0)Thread.Yield或自旋来补充。

Sleep(0)会出让剩余的 CPU 时间片给优先级相同的线程,而Thread.Yield是出让剩余的 CPU 时间片给运行在同一核心上的线程。在出让的时间片结束后,其会被重新调度。一般情况下,整个过程可以在 1ms 之内完成。

Thread.Sleep(0)Thread.Yield在 CPU 高负载情况下非常不稳定,实测可能会阻塞高达 6ms 时间,所以可能会产生更多的误差。因此误差修正最好通过自旋方式实现。

WaitHandle.WaitOne

WaitHandle.WaitOneThread.Sleep类似,参数也是毫秒单位。

不同之处是,没有任务负载的情况下(纯粹循环调用WaitOne(1)),阻塞时长稳定在 1.5ms;而有任务负载时,则可能仅阻塞近乎于 0 的时间(猜测是它仅阻塞到当前时间片结束,尚未找到具体的文档说明)。所以它阻塞的时长范围是 0 到 2ms 多。

WaitHandle.WaitOne(0)是用来测试等待句柄状态的,它并不阻塞,所以用它来进行误差修正类似于自旋,但不如直接使用自旋可靠。

Socket.Poll

Socket.Poll方法的参数是以微秒为单位,理论上,它是使用了网卡的硬件来定时,精度很高。然而,由于阻塞的实现仍然要依赖线程,所以它也只能达到 1ms 的精度。

它的优势是比Thread.SleepWaitHandle.WaitOne要更稳定,误差也更小,可以不需要修正,但要占用一个 Socket 端口。

没有任务负载的情况下(纯粹循环调用Poll(1)),阻塞时长稳定在 1ms;而有任务负载时,则和WaitOne类似,可能仅阻塞近乎于 0 的时间。所以它阻塞的时长范围是 0 到 1ms 多。

Socket.Poll(0)是用来测试 Socket 状态的,但它会阻塞,而且可能阻塞高达 6ms,所以不能用它来进行误差修正。

timeSetEvent

timeSetEvent和之前提到的timeBeginPeriod一样属于 winmm.dll 提供的多媒体定时器功能。它可以直接当作定时器使用,也是提供 1ms 的精度。在不需要的时候使用timeKillEvent来关闭。

它的稳定性和精度也很高,如果需要 1ms 的定时,而又不能使用自旋,那么这是最理想的方案。

虽然 MSDN 上说timeSetEvent是个过时的方法,应该用CreateTimerQueueTimer替换。但是CreateTimerQueueTimer精度和稳定性都不如多媒体定时器,所以在需要高精度的时候,只能使用timeSetEvent

3定时器实现

需要注意的是,无论自旋还是阻塞,显然定时器都应该运行在独立的线程,不能干扰使用方线程工作。而对于高精度定时器来说,触发事件以执行任务的线程一般都在定时器线程内,而不是再使用独立的任务线程。

这是因为高精度定时场景下,执行任务的时间开销很可能大于定时器的时间间隔,如果默认就在其它线程执行任务,可能导致占用大量线程。所以应该把控制权交给用户,让用户在需要的时候自行调度任务执行的线程。

3.1触发模式

由于在定时器线程执行任务,所以定时器的触发就产生了三种模式。以下是它们的说明和主循环伪代码:

  • 固定时间框架

  • 比如间隔 10ms,任务 7-12ms,则会按照等待 10ms 、任务 7ms、等待 3ms、任务 12ms(超时 2ms 失去同步)、任务 7ms、等待 1ms(回到同步)、任务 7ms、等待 3ms、… 进行。就是尽量按照设定好的时间框架来执行任务,只要任务不是始终超时,就可以回到原本的时间框架上。

var 下一帧时间 = 0;while(定时器开启){下一帧时间 += 间隔时间;while (当前计时 < 下一帧时间){等待;}触发任务;}
  • 可推迟时间框架

  • 上面的例子会按照等待 10ms 、任务 7ms、等待 3ms、任务 12ms(超时,推迟时间框架 2ms)、任务 7ms、等待 3ms、… 进行。超时的任务会推迟时间框架。

var 下一帧时间 = 0;while(定时器开启){下一帧时间 += 间隔时间;if (下一帧时间 < 当前计时)下一帧时间 = 当前计时while (当前计时 < 下一帧时间){等待;}触发任务;}
  • 固定等待时间

  • 上面的例子会按照等待 10ms、任务 7ms、等待 10ms、任务 12ms、等待 10ms、任务 7ms… 进行。等待时间始终不变。

while(定时器开启){var 等待开始时间 = 当前计时;while ((当前计时 - 等待开始时间) < 间隔时间){等待;}触发任务;}// 或者:var 下一帧时间 = 0;while(定时器开启){下一帧时间 += 间隔时间;while (当前计时 < 下一帧时间){等待;}触发任务;下一帧时间 = 当前计时;}

如果使用多媒体定时器(timeSetEvent),它固定实现了第一种模式,而其它的等待策略能够实现全部三种模式,可以根据需求选择。

while循环中的等待可以使用自旋或阻塞,也可以结合它们来达到精度、稳定性和 CPU 开销的平衡。

另外,由上面的伪代码可以看出,这三种模式的实现可以统一,能够做到根据情况切换。

3.2线程优先级

最好把线程优先级调高,以保证定时器能够稳定工作,减少被抢占的机会。然而需要注意,这在 CPU 资源不足时可能导致低优先级线程的饥饿。也就是说不能让高优先级线程去等待低优先级线程改变状态,很有可能低优先级线程没有机会运行,导致死锁或类似死锁的状态。(见一种类似的饥饿的例子)

线程的最终优先级和进程的优先级有关,所以有时候也需要提高进程优先级(见 C# 中的多线程系列的线程优先级说明)。

4其它

还有两点需要注意:

  1. 线程安全:定时器在独立线程运行,其暴露的成员都应该实现线程安全,否则在定时器运行时调用可能会产生问题。

  2. 及时释放资源:多媒体定时器、等待句柄、线程等等这些都是系统资源,在不需要它们的时候应该及时释放/销毁。

如何查看系统定时器精度?

简单的查看可以使用Sysinternals工具包中的 ClockRes,它会显示如下信息:

Maximum timer interval: 15.625 msMinimum timer interval: 0.500 msCurrent timer interval: 15.625 ms// 或Maximum timer interval: 15.625 msMinimum timer interval: 0.500 msCurrent timer interval: 1.000 ms

如果是想查看哪些程序请求了更高的系统定时器精度,那么运行:

powercfg energy -duration 5

它会监视系统能耗 5s,然后在当前目录生成一个energy-report.html的分析报告,可以打开它查看。

找到里面的警告部分,会有平台计时器分辨率:未完成的计时器请求Platform Timer Resolution:Outstanding Timer Request)信息。

感谢你能够认真阅读完这篇文章,希望小编分享的".net framework如何实现高精度定时器"这篇文章对大家有帮助,同时也希望大家多多支持,关注行业资讯频道,更多相关知识等着你来学习!

时间 定时器 任务 线程 阻塞 精度 系统 优先级 高精 高精度 运行 情况 状态 误差 多媒体 时候 框架 循环 调度 操作系统 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 深圳互联网科技创新园区 数据库Fmin是什么 一般国内的数据库引擎 网络安全教育征文800字复制 hadoop 数据库 集成电路布图是软件开发 软件开发sa文档与sr 服务器繁忙4002是什么意思 无线接入网络技术 网络技术基础尚晓航第四版 做好网络安全的重要意义 网络安全知识黑板报字内容 迅游服务器管理系统 苏州比较有名的网络安全公司 筑客网络技术上海有限公司 我的世界沙雕服务器的日常动画 比奇网络技术是真的吗 触犯网络安全法是刑事案件 数据库where等于两个 信息科技有限公司互联网广告 数据库安全风险排查 郑州八点一刻网络技术有限公司 惠普服务器设置管理口地址 中国影响力人物数据库正规吗 郑州聪保网络技术有限公司 广州网络安全管理局 数字城管网络安全管理制度 mac网络安全性设置更改 数据库中表格 数据库查询和表有什么区别
0