Search code examples
c#.net-coretimerpolling

High frequency polling loop in C#?


I'm trying to create a high-frequency polling loop in C#, but I've issues with accuracy and CPU usage.

Example 1: The loop sleeps for ~15.6ms, instead of the specified 1ms per iteration. I discovered that this is intended due to the Windows clock resolution.

Example 2: The loop is accurate but has a relatively high CPU usage. This seems to be a busy-loop which I tried to fix with Thread.Sleep(0) or Thread.Yield(), but the CPU usage remains relatively high.

Is there a way to create a loop with a high polling rate that is accurate (i.e. isn't affected by the 15ms inaccuracy) and doesn't hog CPU resources?

// Assume my loop is supposed to be 1000hz (1ms per iteration)

void Example1()
{
    int i = 0;
    
    // Sleeps for 15ms instead of 1ms.
    while(true)
    {
      i++;
      Thread.Sleep(1);
    }
}

void Example2()
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    long prevElapsed = 0;

    int i = 0;
    
    while (true)
    {
        // Accurate to 1ms but relative high CPU usage
        // for just incrementing a counter 1000 times per sec.
        if (stopwatch.ElapsedMilliseconds - prevElapsed >= 1)
        {
            i++;
            prevElapsed = stopwatch.ElapsedMilliseconds;
        }
    }
}

I've tried different methods, but none have been both accurate (close to 1ms per iteration) and CPU efficient:

  • Thread.Sleep(1); (inaccurate)
  • await Task.Delay(1); (inaccurate)
  • System.Timers.Timer; (inaccurate)
  • System.Threading.Timer; (inaccurate)
  • Stopwatch with Thread.Yield(), Thread.Sleep(0) or SpinAwait (accurate but still a relative high CPU usage)

Is there a way to achieve a high polling rate where each iteration is closer to 1 millisecond, without hogging the CPU or relying on methods affected by the OS clock resolution?


Solution

  • General-purpose operating systems like Windows do not guarantee strict timing precision.

    But you can use timeBeginPeriod(1) to improve Sleep/Delay resolution, mind you: Setting the system timer resolution to 1 ms can increase overall system power consumption and affect battery life on laptops. Use it only when needed.

    I think you can work from the below and it should work

    [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", ExactSpelling = true)]
    public static extern uint TimeBeginPeriod(uint uPeriod);
    
    [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", ExactSpelling = true)]
    public static extern uint TimeEndPeriod(uint uPeriod);
    
    // ...
    
    TimeBeginPeriod(1);
    try
    {
        // Now Thread.Sleep(1) or Task.Delay(1) will typically
        // sleep ~1-2 ms instead of ~15 ms (on most systems).
    }
    finally
    {
        TimeEndPeriod(1);
    }
    

    Windows still doesn’t guarantee you’ll get exactly 1.000 ms sleeps, but in practice you often get around 1–2 ms instead of 15+ ms