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?
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