如何在C# Windows Service中最精准地执行35ms间隔的周期性代码?
高精度定时读取CNC数据的优化方案
现有方案的局限性
你当前使用的System.Timers.Timer存在以下精度问题:
- 依赖系统线程池调度,当服务器负载较高时,回调可能被延迟执行
- Windows系统时钟默认分辨率约为15.625ms,35ms的间隔无法被精确整除,实际执行间隔会有±15ms左右的偏差
- 回调执行如果耗时超过35ms,会导致后续任务堆积,进一步破坏定时精度
更精准的替代方案
1. 使用Windows多媒体定时器(Win32 API)
Windows提供的多媒体定时器可以实现毫秒级精度的定时,适合你的高精度需求。在C#中可以通过P/Invoke调用相关API:
using System; using System.Runtime.InteropServices; using System.Threading; using System.Diagnostics; public class HighPrecisionTimer : IDisposable { private delegate void TimerCallback(uint uTimerID, uint uMsg, IntPtr dwUser, IntPtr dw1, IntPtr dw2); [DllImport("winmm.dll", SetLastError = true)] private static extern uint timeSetEvent(uint uDelay, uint uResolution, TimerCallback lpTimeProc, IntPtr dwUser, uint fuEvent); [DllImport("winmm.dll", SetLastError = true)] private static extern uint timeKillEvent(uint uTimerID); [DllImport("winmm.dll", SetLastError = true)] private static extern uint timeBeginPeriod(uint uPeriod); [DllImport("winmm.dll", SetLastError = true)] private static extern uint timeEndPeriod(uint uPeriod); private uint _timerId; private readonly uint _intervalMs; private readonly Action _onTick; private bool _disposed = false; public HighPrecisionTimer(uint intervalMs, Action onTick) { _intervalMs = intervalMs; _onTick = onTick; // 设置系统时钟分辨率为1ms,最大化定时精度 timeBeginPeriod(1); } public void Start() { if (_timerId != 0) return; _timerId = timeSetEvent(_intervalMs, 1, TimerTickCallback, IntPtr.Zero, 1); // 1表示周期性定时 if (_timerId == 0) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } } public void Stop() { if (_timerId != 0) { timeKillEvent(_timerId); timeEndPeriod(1); _timerId = 0; } } private void TimerTickCallback(uint uTimerID, uint uMsg, IntPtr dwUser, IntPtr dw1, IntPtr dw2) { _onTick?.Invoke(); } public void Dispose() { if (!_disposed) { Stop(); _disposed = true; } } } // 使用示例 public class CNCDataReader { private HighPrecisionTimer _precisionTimer; private readonly Queue<CNCData> _kafkaQueue = new Queue<CNCData>(); private readonly Thread _kafkaSenderThread; private bool _isRunning = true; public CNCDataReader() { // 初始化Kafka发送线程 _kafkaSenderThread = new Thread(SendToKafkaLoop) { Priority = ThreadPriority.AboveNormal, IsBackground = true }; _kafkaSenderThread.Start(); } public void StartReading() { // 将当前线程设为最高优先级,降低被抢占概率 Thread.CurrentThread.Priority = ThreadPriority.Highest; _precisionTimer = new HighPrecisionTimer(35, OnHighPrecisionTick); _precisionTimer.Start(); } public void StopReading() { _precisionTimer?.Dispose(); _isRunning = false; _kafkaSenderThread.Join(); } private void OnHighPrecisionTick() { // 1. 高效读取CNC数据(确保此操作耗时远小于35ms) var current = ReadCNCCurrentValue(); var position = ReadCNCPositionValue(); // 2. 记录高精度时间戳 var timestamp = Stopwatch.GetTimestamp(); var utcTime = DateTime.UtcNow; // 3. 入队,避免阻塞定时回调 lock (_kafkaQueue) { _kafkaQueue.Enqueue(new CNCData(current, position, timestamp, utcTime)); } } private void SendToKafkaLoop() { while (_isRunning) { CNCData data = null; lock (_kafkaQueue) { if (_kafkaQueue.Count > 0) { data = _kafkaQueue.Dequeue(); } } if (data != null) { // 执行Kafka发送逻辑 SendDataToKafka(data); } else { Thread.Sleep(5); // 避免空循环占用CPU } } } // 模拟数据读取方法,实际替换为CNC/PLC通信逻辑 private double ReadCNCCurrentValue() => 12.5; private double ReadCNCPositionValue() => 345.6; // 模拟Kafka发送方法 private void SendDataToKafka(CNCData data) { // 实现Kafka生产者逻辑 } private class CNCData { public double Current { get; } public double Position { get; } public long Timestamp { get; } public DateTime UtcTime { get; } public CNCData(double current, double position, long timestamp, DateTime utcTime) { Current = current; Position = position; Timestamp = timestamp; UtcTime = utcTime; } } }
2. 关键优化点
- 提升系统时钟分辨率:通过
timeBeginPeriod(1)将系统时钟精度提升到1ms,减少定时误差 - 高优先级线程:将执行定时任务的线程设置为
ThreadPriority.Highest,降低被其他线程抢占的概率 - 异步处理Kafka发送:采用生产者-消费者模式,将数据读取与Kafka发送解耦,避免阻塞定时回调
- 记录双时间戳:同时记录
Stopwatch高精度戳和UTC时间,既满足AI分析的时间精度,也保留可读的时间标识
3. 其他注意事项
- 优化CNC数据读取效率:确保远程通信(如PLC协议、TCP连接)足够高效,避免读取操作耗时接近或超过35ms
- 服务器资源隔离:可通过
ProcessThread.SetProcessorAffinity将定时任务线程绑定到特定CPU核心,减少上下文切换影响 - 异常处理:在定时回调和Kafka发送逻辑中添加异常捕获,避免单个错误导致整个服务中断
内容的提问来源于stack exchange,提问作者Mdarende
相关产品推荐
相关产品推荐

