how to set timer resolution from C# to 1 ms?
Asked Answered
L

2

10

I've used this tool and noticed that my Windows Server 2008 R2 Standard has a 15 ms resolution while Windows 8 has a 1 ms resolution timer.

I would prefer to set the Timer Resolution to 1 ms on Windows Server 2008 R2 because I'm running low-latency software on it.

I've found this msdn article, but it doesn't explain how to change the Timer resolution from a C# program. How do I do that?

Lyrist answered 25/2, 2013 at 16:16 Comment(4)
I believe the timer resolution is limited on certain architectures (i.e. you can't just set it lower). Here is an article on how to implement your own high-resolution timer for Windows (with code example).Jarid
@Jarid did you read "Obtaining and Setting Timer Resolution" article refered in the question?Lyrist
Yes. If you read the article I linked to, it explains how the resolution is limited according to the architecture. You can get better resolution, but there are compromises that you have to make as well. (You can't just set an arbitrary resolution without the potential of losing some accuracy).Jarid
This blog article, Windows Timer Resolution: Megawatts Wasted discusses some of the pros and cons of changing the timer using timeBeginPeriod() and timeEndPeriod() and also mentions the change in Windows 8 for timer resolution.Ancilin
U
17

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);
Unific answered 25/2, 2013 at 16:21 Comment(4)
should I call this function every time system restarted? or I should call it just once?Lyrist
Whenever you start the system. It's not persisted - but it is global (i.e. it will affect all processes)!Unific
I've used this method - it affect Sleeps, and OS timer, however Threading.Timer keeps its 15ms resolution - more details in my question here: #23216470Lamond
To be precise, the effect of TimeBeginPeriod lasts until you call TimeEndPeriod or until your process ends. So, you typically call it at process initialization time. If you don't need the timer frequency raised for a while then you should, ideally, call TimeEndPeriod to avoid wasting power, and then call TimeBeginPeriod when you need it raised again.Mildred
S
11

Better code to implement this would be:

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

internal sealed class TimePeriod : IDisposable
{
    private const string WINMM = "winmm.dll";

    private static TIMECAPS timeCapabilities;

    private static int inTimePeriod;

    private readonly int period;

    private int disposed;

    [DllImport(WINMM, ExactSpelling = true)]
    private static extern int timeGetDevCaps(ref TIMECAPS ptc, int cbtc);

    [DllImport(WINMM, ExactSpelling = true)]
    private static extern int timeBeginPeriod(int uPeriod);

    [DllImport(WINMM, ExactSpelling = true)]
    private static extern int timeEndPeriod(int uPeriod);

    static TimePeriod()
    {
        int result = timeGetDevCaps(ref timeCapabilities, Marshal.SizeOf(typeof(TIMECAPS)));
        if (result != 0)
        {
            throw new InvalidOperationException("The request to get time capabilities was not completed because an unexpected error with code " + result + " occured.");
        }
    }

    internal TimePeriod(int period)
    {
        if (Interlocked.Increment(ref inTimePeriod) != 1)
        {
            Interlocked.Decrement(ref inTimePeriod);
            throw new NotSupportedException("The process is already within a time period. Nested time periods are not supported.");
        }

        if (period < timeCapabilities.wPeriodMin || period > timeCapabilities.wPeriodMax)
        {
            throw new ArgumentOutOfRangeException("period", "The request to begin a time period was not completed because the resolution specified is out of range.");
        }

        int result = timeBeginPeriod(period);
        if (result != 0)
        {
            throw new InvalidOperationException("The request to begin a time period was not completed because an unexpected error with code " + result + " occured.");
        }

        this.period = period;
    }

    internal static int MinimumPeriod
    {
        get
        {
            return timeCapabilities.wPeriodMin;
        }
    }

    internal static int MaximumPeriod
    {
        get
        {
            return timeCapabilities.wPeriodMax;
        }
    }

    internal int Period
    {
        get
        {
            if (this.disposed > 0)
            {
                throw new ObjectDisposedException("The time period instance has been disposed.");
            }

            return this.period;
        }
    }

    public void Dispose()
    {
        if (Interlocked.Increment(ref this.disposed) == 1)
        {
            timeEndPeriod(this.period);
            Interlocked.Decrement(ref inTimePeriod);
        }
        else
        {
            Interlocked.Decrement(ref this.disposed);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct TIMECAPS
    {
        internal int wPeriodMin;

        internal int wPeriodMax;
    }
}

Use it via:

using (new TimePeriod(1))
{
    ////...
}
Shirleyshirlie answered 13/6, 2014 at 6:9 Comment(2)
An explanation of WHY it's better would be useful.Clavicytherium
Most code using timeBeginPeriod() will involve code that is called regularly. Surrounding code with 'using' would infer otherwise. I would not do it this way.Teaching

© 2022 - 2024 — McMap. All rights reserved.