繁体   English   中英

日期时间漂移 - 2小时后的奇怪问题

[英]DateTime drifting - weird issue after 2 hours

我有一个线程每40毫秒(25赫兹)生成一个网络数据包,它是一个无限循环(直到被告知停止),我正在使用thread.sleep。

当我构建数据包时,其中一个值是当前的GPS时间,使用DateTime.UtcNow并添加闰秒。

这在我开始时工作正常,但随着时间的推移漂移,大约2小时后,它落后了5秒。

我有一个Symmetrom GPS时间服务器,我正在使用他们的软件作为NTP客户端,它说PC上的累积漂移大约是1.2秒(我注意到的大部分是在PC关闭而不同步时漂移到NTP)。

任何人都知道什么是错的? 我知道thread.sleep不是完美的计时,而Windows不是RTOS,但漂移没有意义,丢帧会。

由于一些专有和ITAR问题,我无法发布代码,但我可以发布一个粗略的大纲:

while(!abort) { 
   currentTime = DateTime.UtcNow + leapSeconds ; 
   buildPacket(currentTime); 
   stream.Write(msg, 0, sendSize); 
   //NetworkStream Thread.Sleep(40); 
}

我在Windows 7并使用Visual Studios 2010。

我认为这是因为while循环执行的时间是40 ms(您的睡眠)+执行构建数据包的代码所需的时间。

您是否尝试过使用System.Threading.Timer? 这样,您的代码将在一个单独的线程中执行,然后计算您的时间。 但是,我认为性能不足以让您的实时应用程序长时间运行。

可能涉及许多开销,包括网络IO。 您可以将创建的时间与此类型分开:

public void Timing()
{
    // while (true) to simplify...
    // You should probably remember the last time sent and adjust the 40ms accordingly
    while (true)
    {
        SendPacketAsync(DateTime.UtcNow);
        Thread.Sleep(40);
    }
}

public Task SendPacketAsync(DateTime timing)
{
    return Task.Factory.StartNew(() => {
        var packet = ...; // use timing
        packet.Send(); // abstracted away, probably IO blocking
    });
}

其他答案都是针对这个问题的......除了你正在睡觉的事实之外,你还有开销。

TPL

如果您在TPL世界中运营,那么您可以创建一个非常简单的解决方案:

while(running)
  await Task.WhenAll(Task.Delay(40), Task.Run(()=>DoIO()));

这是一个很好的解决方案,因为它将等待IO操作( DoIO() ),以防它超过40ms。 它也避免使用Thread.Sleep() ,它总是理想的......

计时器

因此,请使用每40ms触发一次的计时器( System.Threading.Timer )。 这样,您可以构建和发送数据包,但计时器仍在计数。 这里的风险是,如果IO操作时间超过40毫秒,那么您就有竞争条件。

注意

40ms是一个期待准确回调的好时机,但是,假设您认为您需要4ms,那么操作系统可能无法提供这种分辨率。 您需要一个实时操作系统来实现这种准确性。

我猜你有两件事被咬了。

  • 现代Windows中的默认计时器分辨率为10毫秒(尽管不能保证)。 如果你把它留在那,你最多会有很多抖动。 您可以使用多媒体API来增加计时器分辨率。 在MSDN上搜索timeGetDevCaps,timeBeginPeriod和timeEndPeriod。
  • 您应该根据一些固定的开始时间来计算您的睡眠间隔,而不是在每次迭代时睡眠40毫秒。 下面的代码说明了这一点。

这是一些骨架代码:

static void MyFunction()
{
    //
    // Use timeGetDevCaps and timeBeginPeriod to set the system timer
    // resolution as close to 1 ms as it will let you.
    //

    var nextTime = DateTime.UtcNow;

    while (!abort)
    {
        // Send your message, preferably do it asynchronously.

        nextTime = nextTime.AddMilliseconds(40);
        var sleepInterval = nextTime - DateTime.UtcNow;

        // may want to check to make sure sleepInterval is positive.

        Thread.Sleep(sleepInterval);
    }

    //
    // Use timeEndPeriod to restore system timer resolution.
    //
}

我不知道有关多媒体时间* API函数的任何.Net包装器。 您可能需要使用PInvoke从C#中调用它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM