You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 13:15:19