Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

high frequency timing .NET

I'm looking to create a high frequency callback thread. Essentially I need a function to execute at a regular high frequency (up to 100Hz) interval. I realize that windows has a normal thread execution slice is ~15ms. I would like to specify a regular interval that can be faster than 15ms.

This is what I'm trying to accomplish. I have an external device that needs to be messaged at a certain interval. The interval is variable depending on the situation. I expect that I would not ever need more than a 100Hz (10ms) message rate.

I can of course implement a spin loop, however, I was hoping there is a solution that would not require so much wasted resources.

The provided links to questions/answers do not resolve this question. While I agree that the question has been asked several different ways, there has not been a good solution that actually solved the problem.

Most of the answers provided talk to using Stopwatch and performing the timing task manually which is entirely too CPU intensive. The only viable solutions were using the multimedia timers which had a couple pitfalls as Haans mentioned. I have found another solution however that I'll add below. I do not know of the pitfalls at this time but I plan to do some testing and research. I am still interested in comments regarding the solution.


WINAPI call via

BOOL WINAPI CreateTimerQueueTimer(
  _Out_     PHANDLE phNewTimer,
  _In_opt_  HANDLE TimerQueue,
  _In_      WAITORTIMERCALLBACK Callback,
  _In_opt_  PVOID Parameter,
  _In_      DWORD DueTime,
  _In_      DWORD Period,
  _In_      ULONG Flags
);

and

BOOL WINAPI DeleteTimerQueueTimer(
  _In_opt_  HANDLE TimerQueue,
  _In_      HANDLE Timer,
  _In_opt_  HANDLE CompletionEvent
);

Link - http://msdn.microsoft.com/en-us/library/windows/desktop/ms682485%28v=vs.85%29.aspx I'm using PInvoke to accomplish this. This would also be required when dealing with the multimedia timers as well however.

My PInvoke signature for those interested. Pinvoke link

[DllImport("kernel32.dll")]
static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer,
   IntPtr TimerQueue, WaitOrTimerDelegate Callback, IntPtr Parameter,
   uint DueTime, uint Period, uint Flags);

// This is the callback delegate to use.
public delegate void WaitOrTimerDelegate (IntPtr lpParameter, bool TimerOrWaitFired);

[DllImport("kernel32.dll")]
static extern bool DeleteTimerQueueTimer(IntPtr TimerQueue, IntPtr Timer,
   IntPtr CompletionEvent);

use CreateTimerQueueTimer to start a timer callback. Use DeleteTimerQueueTimer to stop the timer callback. This is somewhat flexible as you can create custom queues as well. However, the easiest implementation if only a single instance is needed would be to use the default queue.

I tested this solution along side one using the Stopwatch with a spin loop and the results I received in regards to timing were nearly identical. However, the CPU Load was significantly different on my machine.

Stopwatch with spin loop - ~12-15% constant CPU load (About 50% of one of my cores) CreateTimerQueueTimer - ~3-4% constant CPU load

I also feel the code maintenance will be reduced using the CreateTimerQueueTimer option. as it doesn't require logic to be added to your code flow.

like image 331
galford13x Avatar asked May 19 '13 00:05

galford13x


2 Answers

There is a lot of inaccurate information spread through the links and comments. Yes, by default the clock tick interrupt is 1/64 seconds = 15.625 msec on most machines but it can be changed. Also the reason that some machines appear to operate on another rate. The Windows multi-media api, available from winmm.dll lets you tinker with it.

What you don't want to do is using a Stopwatch. You'll only get an accurate interval measurement out of it when you use it in a hot loop that constantly check if the interval has passed. Windows treats such a thread unkindly when its quantum expires, the thread won't be re-scheduled to run for a while when other threads compete for the processor. This effect is easy to miss since you don't typically debug your code with other processes actively running and burning cpu time.

The function you want to use is timeSetEvent(), it provides a highly accurate timer that can go as low as 1 millisecond. It is self-correcting, reducing the interval if necessary (and possible) to catch up when the previous callback got delayed due to scheduling constraints. Beware however that it is difficult to use, the callback is made from a threadpool thread, similar to System.Threading.Timer, so be sure to use safe interlocking and take countermeasures that ensure that you won't get trouble from re-entrancy.

A completely different approach is timeBeginPeriod(), it alters the clock interrupt rate. That has many side-effects, for one Thread.Sleep() becomes more accurate. Which tends to be the easier solution since you can make that synchronous. Just sleep for 1 msec and you'll get the interrupt rate. Some code to play with that demonstrates the way it works:

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

class Program {
    static void Main(string[] args) {
        timeBeginPeriod(10);
        while (!Console.KeyAvailable) {
            var sw = Stopwatch.StartNew();
            for (int ix = 0; ix < 100; ++ix) Thread.Sleep(1);
            sw.Stop();
            Console.WriteLine("{0} msec", sw.ElapsedMilliseconds);
        }
        timeEndPeriod(10);
    }

    [DllImport("winmm.dll")]
    public static extern uint timeBeginPeriod(int msec);
    [DllImport("winmm.dll")]
    public static extern uint timeEndPeriod(int msec);
}

Output on my machine:

1001 msec
995 msec
999 msec
999 msec
999 msec
991 msec
999 msec
999 msec
999 msec
999 msec
999 msec
990 msec
999 msec
998 msec
...

Do beware however of a problem with this approach, if another process on your machine has already lowered the clock interrupt rate below 10 msec then you will not get a second out of this code. That's very awkward to deal with, you can only make it truly safe by asking for a 1 msec rate and sleeping for 10. Don't run that on a battery operated machine. Do favor timeSetEvent().

like image 137
Hans Passant Avatar answered Sep 19 '22 18:09

Hans Passant


You might want to try the StopWatch, which uses the same hi-performance timers used by DirectX.

like image 24
Ali B Avatar answered Sep 17 '22 18:09

Ali B