How to Set Timer Resolution from C# to 1 Ms

how to set timer resolution from C# to 1 ms?

You can try this:

public static class WinApi
{
/// <summary>TimeBeginPeriod(). See the Windows API documentation for details.</summary>

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("winmm.dll", EntryPoint="timeBeginPeriod", SetLastError=true)]

public static extern uint TimeBeginPeriod(uint uMilliseconds);

/// <summary>TimeEndPeriod(). See the Windows API documentation for details.</summary>

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("winmm.dll", EntryPoint="timeEndPeriod", SetLastError=true)]

public static extern uint TimeEndPeriod(uint uMilliseconds);
}

And use it like this:

WinApi.TimeBeginPeriod(1);

And to go back to how it was:

WinApi.TimeEndPeriod(1);

How to setup timer resolution to 0.5 ms?

NtSetTimerResolution

Example code:

#include <windows.h>

extern "C" NTSYSAPI NTSTATUS NTAPI NtSetTimerResolution(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution);

...

ULONG currentRes;
NtSetTimerResolution(5000, TRUE, ¤tRes);

Link with ntdll.lib.

Why are .NET timers limited to 15 ms resolution?

Perhaps the document linked here explains it a bit. It's kinda dry so I only browsed it quickly :)

Quoting the intro:

The system timer resolution determines
how frequently Windows performs two
main actions:

  • Update the timer tick
    count if a full tick has elapsed.
  • Check whether a scheduled timer object
    has expired.

A timer tick is a notion of elapsed
time that Windows uses to track the
time of day and thread quantum times.
By default, the clock interrupt and
timer tick are the same, but Windows
or an application can change the clock
interrupt period.

The default timer
resolution on Windows 7 is 15.6
milliseconds (ms). Some applications
reduce this to 1 ms, which reduces the
battery run time on mobile systems by
as much as 25 percent.

Originally from: Timers, Timer Resolution, and Development of Efficient Code (docx).

Timer minimum limitation

1 ms or 0.5 ms?

Hans is right, the multimedia timer interface is able to provide down to 1 ms resolution.
See About Multimedia Timers (MSDN), Obtaining and Setting Timer Resolution (MSDN), and this answer for more details about timeBeginPeriod. Note: Don't forget to call the timeEndPeriod to switch back to the default timer resolution when done.

How to do:

#define TARGET_RESOLUTION 1         // 1-millisecond target resolution

TIMECAPS tc;
UINT wTimerRes;

if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR)
{
// Error; application can't continue.
}

wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
timeBeginPeriod(wTimerRes);

// do your stuff here at approx. 1 ms timer resolution

timeEndPeriod(wTimerRes);

Note: This procedure is available to other processes as well and the obtained resolution applies system wide. The highest resolution requested by any process will be active, mind the consequences.

However, you may obtain 0.5 ms resolution by means of the hidden API NtSetTimerResolution().
NtSetTimerResolution is exported by the native Windows NT library NTDLL.DLL. See How to set timer resolution to 0.5ms ? on MSDN. Nevertheless, the true achievable resolution is determined by the underlying hardware. Modern hardware does support 0.5 ms resolution.
Even more details are found in Inside Windows NT High Resolution Timers.

How to do:

#define STATUS_SUCCESS 0
#define STATUS_TIMER_RESOLUTION_NOT_SET 0xC0000245

// after loading NtSetTimerResolution from ntdll.dll:

ULONG RequestedResolution = 5000;
ULONG CurrentResolution = 0;

// 1. Requesting a higher resolution
if (NtSetTimerResolution(RequestedResolution,TRUE,&CurrentResolution) != STATUS_SUCCESS) {
// The call has failed
}

printf("CurrentResolution [100 ns units]: %d\n",CurrentResolution);
// this will show 5000 on more modern platforms (0.5ms!)
// do your stuff here at 0.5 ms timer resolution

// 2. Releasing the requested resolution
switch (NtSetTimerResolution(RequestedResolution,FALSE,&CurrentResolution) {
case STATUS_SUCCESS:
printf("The current resolution has returned to %d [100 ns units]\n",CurrentResolution);
break;
case STATUS_TIMER_RESOLUTION_NOT_SET:
printf("The requested resolution was not set\n");
// the resolution can only return to a previous value by means of FALSE
// when the current resolution was set by this application
break;
default:
// The call has failed

}

Note: The functionality of NtSetTimerResolution is basically mapped to the functions timeBeginPeriod and timeEndPeriod by using the bool value Set (see Inside Windows NT High Resolution Timers for more details about the scheme and all its implications). However, the multimedia suite limits the granularity to milliseconds and NtSetTimerResolution allows to set sub-millisecond values.

Precise capture loop

You see a 15 ms delays when sleeping for 15ms and a 30 ms delays when sleeping for 16ms because SpinWait uses under the hood Environment.TickCount which relies on the system clock which has apparently on your system a 15ms resolution.
You can set the system wide timer resolution by using timeBeginPeriod.
See

  • https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod

and

  • https://randomascii.wordpress.com/2020/10/04/windows-timer-resolution-the-great-rule-change/

for more in depth information about the clock resolution. You can check your current sytem wide timer resolution with clockres from sysinternals.

  • https://learn.microsoft.com/en-us/sysinternals/downloads/clockres

See example output:

C:>clockres

Clockres v2.1 - Clock resolution display utility
Copyright (C) 2016 Mark Russinovich
Sysinternals

Maximum timer interval: 15.625 ms
Minimum timer interval: 0.500 ms
**Current timer interval: 15.625 ms**

When a WPF application is running (e.g. Visual Studio)

Maximum timer interval: 15.625 ms
Minimum timer interval: 0.500 ms
**Current timer interval: 1.000 ms**

then you get a 1ms resolution because every WPF application changes the clock resolution to 1ms. This is also used by some guys as workaround to "fix" the issue.

Repeat two tasks every 0.1 ms and 0.3 ms (Systems.Timer.Timer not precise enough)

It is ugly, but you may have some chance using the stopwatch ElapsedTicks.

Stopwatch w = new Stopwatch()
long nextTick = 0;
long ticksPerMicroSecond = 1000000L / Stopwatch.Frequency; // Number of ticks per microsec.
long periodInTicks = ticksPerMicroSecond / 100; // Ticks per 0.1ms

while(true)
{
long currentTick = w.ElapsedTicks.
if (currentTick > nextTick)
{
nextTick = currentTick + periodInTicks;
// Do something here.
}
}

It is as ugly as it can get, and you get a while(true) loop, that will probably be using 100% cpu for free (better run this on a multi core, then).

You may want to benchmark the thing a bit (look at examples in Stopwatch.Frequency msdn doc), to see how precise your timer can be, and see the drift you get from being late with your inner function. You may also want to enable background gc to avoid GC blocking your thread. While this shows it is dable in code, I'm really not sure you can get your function run in less than 0.1ms.

High resolution timer in C#

There is nothing built into the .NET framework that I am aware of. Windows has a mechanism for high resolution timer events via the Multimedia Timer API. Below is a quick example I whipped up which seems to do the job. There are also seems to be a good example here.

I will note that this API changes system wide settings that can degrade system performance, so buyer beware. For testing purposes, I would recommend keeping track of how often the timer is firing to verify the timing is similar to the device you are trying to simulate. Since windows is not a real-time OS, the load on your system may cause the MM timer be delayed resulting in gaps of 100 ms that contain 100 events in quick succession, rather than 100 events spaced 1 ms apart. Some additional reading on MM timers.

class Program
{
static void Main(string[] args)
{
TestThreadingTimer();
TestMultimediaTimer();
}

private static void TestMultimediaTimer()
{
Stopwatch s = new Stopwatch();
using (var timer = new MultimediaTimer() { Interval = 1 })
{
timer.Elapsed += (o, e) => Console.WriteLine(s.ElapsedMilliseconds);
s.Start();
timer.Start();
Console.ReadKey();
timer.Stop();
}
}

private static void TestThreadingTimer()
{
Stopwatch s = new Stopwatch();
using (var timer = new Timer(o => Console.WriteLine(s.ElapsedMilliseconds), null, 0, 1))
{
s.Start();
Console.ReadKey();
}
}

}

public class MultimediaTimer : IDisposable
{
private bool disposed = false;
private int interval, resolution;
private UInt32 timerId;

// Hold the timer callback to prevent garbage collection.
private readonly MultimediaTimerCallback Callback;

public MultimediaTimer()
{
Callback = new MultimediaTimerCallback(TimerCallbackMethod);
Resolution = 5;
Interval = 10;
}

~MultimediaTimer()
{
Dispose(false);
}

public int Interval
{
get
{
return interval;
}
set
{
CheckDisposed();

if (value < 0)
throw new ArgumentOutOfRangeException("value");

interval = value;
if (Resolution > Interval)
Resolution = value;
}
}

// Note minimum resolution is 0, meaning highest possible resolution.
public int Resolution
{
get
{
return resolution;
}
set
{
CheckDisposed();

if (value < 0)
throw new ArgumentOutOfRangeException("value");

resolution = value;
}
}

public bool IsRunning
{
get { return timerId != 0; }
}

public void Start()
{
CheckDisposed();

if (IsRunning)
throw new InvalidOperationException("Timer is already running");

// Event type = 0, one off event
// Event type = 1, periodic event
UInt32 userCtx = 0;
timerId = NativeMethods.TimeSetEvent((uint)Interval, (uint)Resolution, Callback, ref userCtx, 1);
if (timerId == 0)
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error);
}
}

public void Stop()
{
CheckDisposed();

if (!IsRunning)
throw new InvalidOperationException("Timer has not been started");

StopInternal();
}

private void StopInternal()
{
NativeMethods.TimeKillEvent(timerId);
timerId = 0;
}

public event EventHandler Elapsed;

public void Dispose()
{
Dispose(true);
}

private void TimerCallbackMethod(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2)
{
var handler = Elapsed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}

private void CheckDisposed()
{
if (disposed)
throw new ObjectDisposedException("MultimediaTimer");
}

private void Dispose(bool disposing)
{
if (disposed)
return;

disposed = true;
if (IsRunning)
{
StopInternal();
}

if (disposing)
{
Elapsed = null;
GC.SuppressFinalize(this);
}
}
}

internal delegate void MultimediaTimerCallback(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);

internal static class NativeMethods
{
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")]
internal static extern UInt32 TimeSetEvent(UInt32 msDelay, UInt32 msResolution, MultimediaTimerCallback callback, ref UInt32 userCtx, UInt32 eventType);

[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")]
internal static extern void TimeKillEvent(UInt32 uTimerId);
}

High resolution timer

I found a solution to this problem in the following blog:
http://web.archive.org/web/20110910100053/http://www.indigo79.net/archives/27#comment-255

It tells you how to use the multimedia timer to have a timer with high frequency. It is working just fine for me!!!



Related Topics



Leave a reply



Submit