.NET中实现高精度定时器的思路 |
.NET中有多少种定时器一文介绍过.NET中至少有6种定时器,但精度都不是特别高,一般在15ms'55ms之间 。在一些特殊场景,可能需要高精度的定时器,这就需要我们自己实现了 。本文将讨论高精度定时器实现的思路 。 高精度定时器一个定时器至少需要考虑三部分功能:计时、等待、触发模式 。计时是进行时间检查,调整等待的时间;等待则是用来跳过指定的时间间隔 。触发模式是指定时器每次Tick的时间固定还是每次定时任务时间间隔固定 。比如定时器时间间隔10ms,定时任务耗时7ms,是每隔10ms触发一次定时任务,还是等定时任务执行完后等10ms再触发下一个定时任务 。 计时Windows提供了可用于获取高精度时间戳或者测量时间间隔的API 。系统原生API是 等待等待策略通常有两种:
自旋等待自旋等待可以使用 void Spin(Stopwatch w, int duration) { var current = w.ElapsedMilliseconds; while ((w.ElapsedMilliseconds - current) < duration) Thread.SpinWait(5); } 由于自旋是以消耗CPU为代价的,上述代码运行时,CPU处于满负荷工作状态(使用率持续保持100%左右),因此短暂的等待可以考虑自旋,长时间运行的定时器不太建议使用该方法 。 阻塞等待阻塞等待需要操作系统能够及时把定时器线程调度回运行状态 。默认情况下,Windows的系统的计时器精度为15ms左右 。如果是线程阻塞,出让其时间片进行等待,然后再被调度运行的时间至少是一个时间切片15ms左右 。要通过阻塞实现高精度计时,则需要减少时间切片的长度 。Windows系统API提供了
通常我们使用Thread.Sleep来挂起线程等待,Sleep的参数最小为1ms,但实际上很不稳定,实测发现大部分时候稳定在阻塞2ms 。我们可以采用Sleep(0)或者 void wait(Stopwatch w, int duration) { var current = w.ElapsedMilliseconds; while ((w.ElapsedMilliseconds - current) < duration) Thread.Sleep(0); } Thread.Sleep(0)和Thread.Yield在 CPU 高负载情况下非常不稳定,可能会产生更多的误差 。因此误差修正最好通过自旋方式实现 。 还有一种阻塞的方式是多媒体定时器 public enum TimerError { MMSYSERR_NOERROR = 0, MMSYSERR_ERROR = 1, MMSYSERR_INVALPARAM = 11, MMSYSERR_NOCANDO = 97, } public enum RepeateType { TIME_ONESHOT=0x0000, TIME_PERIODIC = 0x0001 } public enum CallbackType { TIME_CALLBACK_FUNCTION = 0x0000, TIME_CALLBACK_EVENT_SET = 0x0010, TIME_CALLBACK_EVENT_PULSE = 0x0020, TIME_KILL_SYNCHRONOUS = 0x0100 } public class HighPrecisionTimer { private delegate void TimerCallback(int id, int msg, int user, int param1, int param2); [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps")] private static extern TimerError TimeGetDevCaps(ref TimerCaps ptc, int cbtc); [DllImport("winmm.dll", EntryPoint = "timeSetEvent")] private static extern int TimeSetEvent(int delay, int resolution, TimerCallback callback, int user, int eventType); [DllImport("winmm.dll", EntryPoint = "timeKillEvent")] private static extern TimerError TimeKillEvent(int id); private static TimerCaps _caps; private int _interval; private int _resolution; private TimerCallback _callback; private int _id; static HighPrecisionTimer() { TimeGetDevCaps(ref _caps, Marshal.SizeOf(_caps)); } public HighPrecisionTimer() { Running = false; _interval = _caps.periodMin; _resolution = _caps.periodMin; _callback = new TimerCallback(TimerEventCallback); } 'HighPrecisionTimer() { TimeKillEvent(_id); } public int Interval { get { return _interval; } set { if (value < _caps.periodMin || value > _caps.periodMax) throw new Exception("invalid Interval"); _interval = value; } } public bool Running { get; private set; } public event Action Ticked; public void Start() { if (!Running) { _id = TimeSetEvent(_interval, _resolution, _callback, 0, (int)RepeateType.TIME_PERIODIC | (int)CallbackType.TIME_KILL_SYNCHRONOUS); if (_id == 0) throw new Exception("failed to start Timer"); Running = true; } } public void Stop() { if (Running) { TimeKillEvent(_id); Running = false; } } private void TimerEventCallback(int id, int msg, int user, int param1, int param2) { Ticked?.Invoke(); } } 触发模式由于定时任务执行时间不确定,并且可能耗时超过定时时间间隔,定时器的触发可能会有三种模式:固定时间框架,可推迟时间框架,固定等待时间 。
假定时间间隔为10ms,任务执行的时间在7'11ms之间,下图中显示了三种触发模式的区别 。
到此这篇关于.NET中如何实现高精度定时器的文章就介绍到这了,更多相关.NET定时器内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持! |