简体   繁体   中英

DateTime drifting - weird issue after 2 hours

I have a thread to generate a network packet every 40 ms (25 Hz), it's an infinite loop (until told to stop), and I'm using thread.sleep.

When I build the packet, one of the values is the current GPS time, using DateTime.UtcNow and adding the leap seconds.

This works fine when I start, but it drifts with time, about 2 hours later, it's 5 seconds behind.

I have a Symmetrom GPS Time Server and I'm using their software as the NTP client, and it says the cumulative drift on the PC is about 1.2 seconds (most of that I've noticed is drift while the PC is off and not syncing to NTP).

Anyone have any idea whats going wrong? I know thread.sleep isn't perfect timing, and Windows isn't an RTOS, but the drift doesn't make sense, dropping frames would.

I can't post code due to some proprietary and ITAR issues, but I can post a rough outline:

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

I'm in Windows 7 and using Visual Studios 2010.

I think this happens because the time that a while loop executes is 40 ms (your sleep) + the time necessary to execute the code that builds the packet.

Have you tried using a System.Threading.Timer ? This way your code will execute in a separate thread then the one that is counting your time. However, I don't think the performance is good enough to keep your real time application running for long.

There is probably many overhead involved, including network IO. You could decouple the timing from the creation like this:

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
    });
}

The other answers are on to the problem... You have overhead on top of the fact that you are sleeping.

TPL

If you operate in the world of TPL , then you can create a pretty simple solution:

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

This is a great solution because it will wait for the IO operation ( DoIO() ) in case it takes longer than 40ms. It also avoids using Thread.Sleep() which is always ideal...

Timer

So instead, use a timer ( System.Threading.Timer ) that will fire every 40ms. This way, you can be building and sending the packet, but the timer is also still counting. The risk here is if the IO operation takes longer than 40ms, you have a race condition.

NOTE

40ms is an OK time to expect an accurate callback, HOWEVER, lets say you decided you needed 4ms, then the OS probably wouldn't be able to provide this kind of resolution. You would need a real-time OS for this kind of accuracy.

I would guess you are being bitten by two things.

  • The default timer resolution in modern Windows is 10 ms (although there are no guarantees about that). If you leave it at that you will, at best, have lots of jitter. You can use the multimedia API to increase the timer resolution. Search MSDN for timeGetDevCaps, timeBeginPeriod and timeEndPeriod.
  • You should calculate your sleep interval based on some fixed start time, rather than sleeping 40 ms on every iteration. The code below illustrates that.

Here is some skeletal code:

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.
    //
}

I don't know of any .Net wrappers for the multimedia time* API functions. You will probably need to use PInvoke to call them from C#.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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