2

I'm writing an RTP server to publish PCMA wave files. It needs to pump data every 20ms (on average - it can be a bit either side of that for any 1 pump, but must average out at 20ms).

My current implementation uses a Timer, but then the event fires just over every 20 ms, so it gradually drifts out.

Is there a better way to do this? The only way I can currently think of is to dynamically adjust the timer inteval as it starts to creep, in order to bring it back in line.

Sample Code

void Main()
{
    System.Timers.Timer timer = new System.Timers.Timer();

    // Use a stopwatch to measure the "wall-clock" elapsed time.
    Stopwatch sw = new Stopwatch();
    sw.Start();

    timer.Elapsed += (sender, args) => 
    {
        Console.WriteLine(sw.ElapsedMilliseconds);
        // Simulate doing some work here - 
        // in real life this would be pumping data via UDP.
        Thread.Sleep(300);
    };

    timer.AutoReset = true;
    // I'm using an interval of 1 second here as it better 
    // illustrates the problem
    timer.Interval = 1000;
    timer.Start();
}

Output:

1002
2001
3002
4003
5003
6005
7006
8007
9007
10017
11018
12019
13019
14020 <-- By this point we have creeped over 20 ms in just 14 iterations :(
RB.
  • 36,301
  • 12
  • 91
  • 131
  • Just FYI, the term you're looking for is *drift* :-) – Cameron Jul 20 '14 at 05:51
  • @Cameron Thanks - it was 5AM UK time when I wrote this question ;-) – RB. Jul 20 '14 at 06:12
  • WinAPI has a solution for this, the waitable timer. I don't think there's a .NET wrapper, though. – Ben Voigt Jul 20 '14 at 07:28
  • @Ben Could I p/invoke it? Can you add a couple of links as an answer? – RB. Jul 20 '14 at 16:05
  • 1
    @RB.: You can p/invoke the functions for accessing it, but .NET's message loop doesn't support waiting on handles. In a typical C/C++ application, it's as straightforward as replacing `GetMessage` or `PeekMessage` with `MsgWaitForMultipleObjects`. .NET's message loop is pretty fancy and interwoven with a bunch of other Windows Forms features, like message filters, so replacing the whole thing isn't really feasible. At its core it is just `GetMessage`, so upgrading it would be easy, if we had the ability to edit it. But it's part of System.Windows.Forms.dll – Ben Voigt Jul 20 '14 at 16:21

2 Answers2

2

First of all: I will never get it to be exact because your program will never be in full control of what the CPU's are doing as long as you are running on standard Windows because it is not a real-time OS. Just think of a anti virus kicking in, the Garbage Collector freezing your thread, playing a game on the side, ...

That said you might be able to compensate a bit.

When the handler kicks in, pause the timer, record the current time, act, update the time's interval by setting the interval to the required interval based upon the start of the handler and the time it has taken to act.

This way you can control the creeping better. An exception to that might be when the acting takes longer than the interval and whether the interval should contain the time to act or be the time between to acts.

In my experience you cannot rely on any timer to get an interval that small (20 ms) accurately but compensating for creep can help quite a bit.

Emond
  • 50,210
  • 11
  • 84
  • 115
  • Yeah - that's what I meant when I said dyamically adjust the timer interval. You can actually do it without pausing the timer - just add a handler whos job is to work out the next appropriate interval and set the `((Timer)sender).Interval -= drift`. – RB. Jul 20 '14 at 06:14
  • If you do not pause the timer (or rather the triggering of the handler) you might get into a race condition if acting takes longer than the interval and the timer ticks again when there is a thread executing the work ending up with multiple threads doing the same job... – Emond Jul 20 '14 at 06:17
  • Good point - I'll test around that scenario. In practice, if it's takng me longer than a timer interval to pump my data I've got much bigger problems anyway as I'll never be able to service the clients in time! – RB. Jul 20 '14 at 06:21
  • Again: the work you are doing might not take so much time but the thread could simply not be running as other thread get CPU time – Emond Jul 20 '14 at 06:23
  • I'm pumping data via UDP, and the client is responsible for sorting out packets if they arrive in order, so it should be fine (and, in fact, is actually the behaviour I am looking for) - I just need to ensure I perform some tests around this. – RB. Jul 20 '14 at 06:35
  • As a side note, and from experience: it might be that drift is not a big deal and that 'up-to-dateness' is far more important. Make sure you are solving the right problem! – Emond Jul 20 '14 at 08:07
  • In this case I'm pumping data at a fixed rate (160 bytes per 20ms), but generally yes, that would be good advice :-) – RB. Jul 20 '14 at 16:04
0

You could use StopWatch to measure time, but it doesn't have callbacks.

You can use Windows multimedia timer. It involves some WinAPI, but all the details are provided in this article.

Community
  • 1
  • 1
sasha_gud
  • 1,635
  • 13
  • 18