C# 日期时间漂移-2小时后出现奇怪问题

C# 日期时间漂移-2小时后出现奇怪问题,c#,.net,timing,C#,.net,Timing,我有一个线程,每40毫秒(25赫兹)生成一个网络数据包,这是一个无限循环(直到被告知停止),我正在使用thread.sleep 构建数据包时,其中一个值是当前GPS时间,使用DateTime.UtcNow并添加闰秒 这在我开始的时候效果很好,但它会随着时间而漂移,大约2小时后,它落后了5秒 我有一个Symmetrom GPS时间服务器,我使用他们的软件作为NTP客户端,它说PC上的累积漂移约为1.2秒(我注意到的大部分是在PC关闭且未同步到NTP时的漂移) 有人知道出了什么问题吗?我知道thre

我有一个线程,每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 Studio 2010。

我认为这是因为while循环执行的时间是40毫秒(您的睡眠时间)+执行生成数据包的代码所需的时间


您是否尝试过使用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()
),以防超过40毫秒。它还避免了使用总是理想的
Thread.Sleep()

计时器

因此,请改用计时器(
System.Threading.timer
)每隔40毫秒触发一次。通过这种方式,您可以构建和发送数据包,但计时器仍在计数。这里的风险是,如果IO操作所需时间超过40毫秒,则存在竞争条件

注意


40毫秒是期望准确回调的好时机,但是,假设您决定需要4毫秒,那么操作系统可能无法提供这种分辨率。你需要一个实时操作系统来达到这种精度。

我猜你正被两件事困扰着

  • 现代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.
    //
}

我不知道有什么.Net包装器可以用于多媒体时间*API函数。您可能需要使用PInvoke从C#呼叫他们。

您正在使用
Sleep(40)
,然后做什么?你考虑过构建数据包所需的时间吗?考虑到你有超过18万个滴答声,我认为5秒并不是那么糟糕。即使在Thread.Sleep()中有一个非常小的不精确性,随着时间的推移也会导致一个很大的间隔。Windows计时器的情况更糟,30分钟后你可以休息10秒。所以你基本上是说两小时后,系统时间与GPS接收器的时间相差5秒?这仅仅表明时钟同步不起作用或者没有足够频繁地进行。保持PC时钟与GPS接收机同步需要不断的时钟校准。尝试将数据包的构建和发送与时间测量分离。从你的循环线程启动一个单独的线程来做这项工作。@Rafael Nope,经典计时器的准确度是55毫秒,这超过了OP的时间跨度。另外,因为线程并没有在你想要的精确毫秒唤醒,但当操作系统给它机会唤醒时(这取决于许多因素,如当前进程计数)