18

I want to have a timer with about 5millisecond resolution. but the current Timer in .Net has a resolution of about 50ms. I could not find any working solution that creates a high resolution timer, although some claim that you can do it in C#.

Carlo V. Dango
  • 13,322
  • 16
  • 71
  • 114
sura
  • 1,471
  • 4
  • 17
  • 25

8 Answers8

15

In regards to the information that the OP was specifically asking about the Timer class which fires events at regular intervals. I have amended this answer, with my old answer below the horizontal rule.

I tested the following code with the Timer class, and it seems like it can get at least within the 14 - 15 millisecond range on my machine. Try it out for yourself and see if you can reproduce this. So sub-50 millisecond response times are possible, but it can't get down to exactly one millisecond.

using System;
using System.Timers;
using System.Diagnostics;

public static class Test
{
    public static void Main(String[] args)
    {
        Timer timer = new Timer();
        timer.Interval = 1;
        timer.Enabled = true;

        Stopwatch sw = Stopwatch.StartNew();
        long start = 0;
        long end = sw.ElapsedMilliseconds;

        timer.Elapsed += (o, e) =>
        {
            start = end;
            end = sw.ElapsedMilliseconds;
            Console.WriteLine("{0} milliseconds passed", end - start);
        };

        Console.ReadLine();
    }
}

NB: The following is my old answer, when I thought the OP was talking about timing things. The following is merely useful information with respect to timing the duration of things, but doesn't provide any way of firing events at a regular interval. For that purpose, the Timer class is necessary.

Try using the Stopwatch class within System.Diagnostics: http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

You can query it to check if it's high resolution through it's IsHighResolution field. Also, you can check the exact resolution of the Stopwatch:

int resolution = 1E9 / Stopwatch.Frequency;
Console.WriteLine("The minimum measurable time on this system is: {0} nanoseconds", resolution);

If you're worried about where this is actually sourced, the documentation seems to imply that it actually internally calls the lower level Win32 functions:

The Stopwatch class assists the manipulation of timing-related performance counters within managed code. Specifically, the Frequency field and GetTimestamp method can be used in place of the unmanaged Win32 APIs QueryPerformanceFrequency and QueryPerformanceCounter.

Mike Bailey
  • 12,479
  • 14
  • 66
  • 123
  • from what i understand, stopwatch can only be used to accurately measure elapsed time. But my timer wants to get fired (say) every 5 milliseconds, and a method has to get executed. I don't see i can do this using stopwatch class. – sura Aug 21 '11 at 21:58
  • Oops. I was under the impression that you meant for timing things, not necessarily for driving events. I'll look around to see if there are any solutions to this issue. – Mike Bailey Aug 22 '11 at 03:57
  • @sura: I just checked the documentation for the `Timer` class on the interval property: http://msdn.microsoft.com/en-us/library/system.timers.timer.interval.aspx. It seems like it can go down to at least millisecond precision, by specifying an interval of 1. When you set this, does it still fire much slower than usual? – Mike Bailey Aug 22 '11 at 03:59
  • This is what I was using, but it can go down until upto 55ms intervals, ans the timer interval is not much precise. Plus, I read somewhere that it introduces lot of performance overhead when the interval is set to a very low level. anyway, i found a solution which uses the Multimedia timer. check my answer postat the end. – sura Aug 22 '11 at 04:44
11

What about this one?

public class HiResTimer
{
    private bool isPerfCounterSupported = false;
    private Int64 frequency = 0;

    // Windows CE native library with QueryPerformanceCounter().
    private const string lib = "coredll.dll";
    [DllImport(lib)]
    private static extern int QueryPerformanceCounter(ref Int64 count);
    [DllImport(lib)]
    private static extern int QueryPerformanceFrequency(ref Int64 frequency);

    public HiResTimer()
    {
        // Query the high-resolution timer only if it is supported.
        // A returned frequency of 1000 typically indicates that it is not
        // supported and is emulated by the OS using the same value that is
        // returned by Environment.TickCount.
        // A return value of 0 indicates that the performance counter is
        // not supported.
        int returnVal = QueryPerformanceFrequency(ref frequency);

        if (returnVal != 0 && frequency != 1000)
        {
            // The performance counter is supported.
            isPerfCounterSupported = true;
        }
        else
        {
            // The performance counter is not supported. Use
            // Environment.TickCount instead.
            frequency = 1000;
        }
    }

    public Int64 Frequency
    {
        get
        {
            return frequency;
        }
    }

    public Int64 Value
    {
        get
        {
            Int64 tickCount = 0;

            if (isPerfCounterSupported)
            {
                // Get the value here if the counter is supported.
                QueryPerformanceCounter(ref tickCount);
                return tickCount;
            }
            else
            {
                // Otherwise, use Environment.TickCount.
                return (Int64)Environment.TickCount;
            }
        }
    }

    static void Main()
    {
        HiResTimer timer = new HiResTimer();

        // This example shows how to use the high-resolution counter to 
        // time an operation. 

        // Get counter value before the operation starts.
        Int64 counterAtStart = timer.Value;

        // Perform an operation that takes a measureable amount of time.
        for (int count = 0; count < 10000; count++)
        {
            count++;
            count--;
        }

        // Get counter value when the operation ends.
        Int64 counterAtEnd = timer.Value;

        // Get time elapsed in tenths of a millisecond.
        Int64 timeElapsedInTicks = counterAtEnd - counterAtStart;
        Int64 timeElapseInTenthsOfMilliseconds =
            (timeElapsedInTicks * 10000) / timer.Frequency;

        MessageBox.Show("Time Spent in operation (tenths of ms) "
                       + timeElapseInTenthsOfMilliseconds +
                       "\nCounter Value At Start: " + counterAtStart +
                       "\nCounter Value At End : " + counterAtEnd +
                       "\nCounter Frequency : " + timer.Frequency);
    }
}
genesis
  • 50,477
  • 20
  • 96
  • 125
  • 5
    +1 for seemingly having this large class on hand. -1 for being unnecessarily complicated, when the `Stopwatch` class within `System.Diagnostics` accomplishes the same. I'm not sure if they exposed this in VS 2005, but it exists now and there's no reason to use something like this. – Mike Bailey Aug 21 '11 at 08:04
  • 5
    I don't know how precise Stopwatch can be, but the performance counters get you into the low microsecond range. I also wonder what makes this a "large" or "complicated" class by all means. Should I say "-1" for that? – Razzupaltuff Aug 21 '11 at 08:12
  • 5
    To quote the documentation: `The Stopwatch class assists the manipulation of timing-related performance counters within managed code. Specifically, the Frequency field and GetTimestamp method can be used in place of the unmanaged Win32 APIs QueryPerformanceFrequency and QueryPerformanceCounter.` So there's no need to use this sort of code. Also, this is "large" in the sense that the Stopwatch is MUCH simpler to use. – Mike Bailey Aug 21 '11 at 08:14
  • To be fair Genesis was just cut-n-pasting the class in the link he provided. StopWatch do use performance counter API under the hood if available (at least in .NET4). If not available it falls back on DateTime.Now. – Just another metaprogrammer Aug 21 '11 at 08:17
  • 3
    He wanted working solution, so I gave him one. I see no reason for downvote as this IS working – genesis Aug 21 '11 at 08:18
  • Yeah, the documentation says that Stopwatch uses a high res counter when one is available. – Razzupaltuff Aug 21 '11 at 08:18
  • @FuleSnabel: Fair enough, I hadn't realized there was a link until after I posted my comment. Looking back to the documentation for Stopwatch, it says the same about the internal workings all the way back to .NET 2.0 – Mike Bailey Aug 21 '11 at 08:19
  • 3
    @genesis: It is a working solution, but I would advise away from it as the `Stopwatch` is a more idiomatic (not to mention more straight forward) way of accomplishing the same thing. I wouldn't give this a -1, but I would caution people away from this and to look towards the Stopwatch instead. – Mike Bailey Aug 21 '11 at 08:20
  • @MikeBantegui: I could change my post to suggest Stopwatch, but it would be "duplicate" of your post, huh? – genesis Aug 21 '11 at 08:21
  • @genesis: Yes you could. Your solution is fine, but it's just outdated now that the Stopwatch class exists. I don't think you should change your answer at all, just so people know that both exist. – Mike Bailey Aug 21 '11 at 08:22
  • This code can only work on Windows Mobile, a dead operating system. – Hans Passant Aug 21 '11 at 09:49
  • i think that this will not solve my problem exactly, as I explained in my comment for the previous answer. I need a timer that triggers every 5 milliseconds, and calls a method. – sura Aug 21 '11 at 22:15
  • No, this code works on far more than Windows Mobile. This is a standard idiom for high performance. Stopwatch has made the same claims for a long time (back to .Net 2.0 apparently) but it hasn't always been true. I used precisely this type of code under .Net 3.0 precisely because, while Stopwatch claimed it would use high performance counters if available, if you actually ran both Stopwatch was only within about 50 milliseconds, but the method in this answer would give you near microsecond timing. If StopWatch really does what it claims now, then great, but it certainly hasn't always. –  Sep 06 '16 at 16:46
7

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

cHao
  • 84,970
  • 20
  • 145
  • 172
sura
  • 1,471
  • 4
  • 17
  • 25
  • If this indeed did solve your problem, you should accept this as the correct answer. In this circumstance, it's completely fine to accept your own answer since none of the other ones posted here seem to solve your problem. – Mike Bailey Aug 22 '11 at 04:48
  • 2
    Yep, indeed the link is broken. See this is why I cringe on posts that links to external websites as an answer and don't realize that the external website or webpage could any moment become broken as in this case. Needless to say, Sura's answer is useless to the StackOverflow community NOW, even though it worked for him or her then. – ThN Oct 31 '12 at 14:22
  • yep , but with mentioning multimedia timers you can think of a search term. Copy & pasting codes from other sites is not legal in some countries, so a link is a legal safe thingy – Offler Jan 22 '13 at 12:11
  • For reference: http://svn.the-starport.net/utfeditor/UTFEditor/MultimediaTimer.cs . The blog article seems to have had some impact on people. :) – cHao Aug 21 '13 at 01:49
  • Here is a link and code from codeproject: [link](http://www.codeproject.com/Articles/2635/High-Performance-Timer-in-C) The code was to long to add. But it's using the QueryPerformanceCounter() and the QueryPerformanceFrequency(). You can just Google them if the link breaks. – Jonas Aug 18 '15 at 11:26
  • StopWatch works fine also. – Jonas Aug 18 '15 at 12:02
5

Here is an implementation based on StopWatch timer
https://gist.github.com/DraTeots/436019368d32007284f8a12f1ba0f545

  1. It works on all platforms and is high precision wherever StopWatch.IsHighPrecision == true

  2. Its Elapsed event is guaranteed to be non overlapping (which might be important to know, because state changes inside the event handler may be left unprotected against multi threaded access)

Here is how to use it:

Console.WriteLine($"IsHighResolution = {HighResolutionTimer.IsHighResolution}");
Console.WriteLine($"Tick time length = {HighResolutionTimer.TickLength} [ms]");

var timer = new HighResolutionTimer(0.5f);

// UseHighPriorityThread = true, sets the execution thread 
// to ThreadPriority.Highest.  It doesn't provide any precision gain
// in most of the cases and may do things worse for other threads. 
// It is suggested to do some studies before leaving it true
timer.UseHighPriorityThread = false;

timer.Elapsed += (s, e) => { /*... e.Delay*/ }; // The call back with real delay info
timer.Start();  
timer.Stop();    // by default Stop waits for thread.Join()
                 // which, if called not from Elapsed subscribers,
                 // would mean that all Elapsed subscribers
                 // are finished when the Stop function exits 
timer.Stop(joinThread:false)   // Use if you don't care and don't want to wait

Here is a benchmark (and a live example):
https://gist.github.com/DraTeots/5f454968ae84122b526651ad2d6ef2a3

The results of setting the timer for 0.5 ms on Windows 10: enter image description here

It is also worth to mention that:

  1. I had the same precision on mono on Ubuntu.

  2. While playing with the benchmark, the maximum and a very rare deviation I saw was about 0.5 ms (which probably means nothing, it is not realtime systems, but still worth mentioning)

  3. Stopwatch ticks are not TimeSpan ticks. On that Windows 10 machine HighResolutionTimer.TickLength is 0.23[ns].

  4. CPU usage of the benchmark is 10% for 0.5ms interval and 0.1% for 200ms interval

MajesticRa
  • 13,770
  • 12
  • 63
  • 77
2

Pretty late to the party still might be useful for someone looking for an answer as nothing has been changed for over a decade in the topic.

Background

Any .NET delay instructions would always come down to system clock resolution, the one you set with timeBeginPeriod(). No matter if it's a Thread.Sleep(N), Threading.Timer or Waitable.WaitOne(N). Yet time resolution of DateTime.Now() and System.Diagnostic.Stopwatch is way higher so there is a way of implementing precise timing events known as hot loop. Hot loops yet prone to being threatened badly by OS since those tend to occupy processor core at full. And here's what we do to prevent this:

Surrender thread time quant in a hot loop to other threads once there's no more need for it by calling Thread.Sleep(0) or .WaitOne(0)

Below is a code piece showing simple implementation of high resolution scheduler:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// High resolution scheduler. 
/// License: public domain (no restrictions or obligations)
/// Author: Vitaly Vinogradov
/// </summary>
public class HiResScheduler : IDisposable
{
    /// <summary>
    /// Scheduler would automatically downgrade itself to cold loop (Sleep(1)) when there are no
    /// tasks earlier than the treshold. 
    /// </summary>
    public const int HOT_LOOP_TRESHOLD_MS = 16;

    protected class Subscriber : IComparable<Subscriber>, IComparable
    {
        public Action Callback { get; set; }
        public double DelayMs { get; set; }

        public Subscriber(double delay, Action callback)
        {
            DelayMs = delay;
            Callback = callback;
        }

        public int CompareTo(Subscriber other)
        {
            return DelayMs.CompareTo(other.DelayMs);
        }

        public int CompareTo(object obj)
        {
            if (ReferenceEquals(obj, null))
                return -1;
            var other = obj as Subscriber;
            if (ReferenceEquals(other, null))
                return -1;
            return CompareTo(other);
        }
    }

    private Thread _spinner;
    private ManualResetEvent _allowed = new ManualResetEvent(false);
    private AutoResetEvent _wakeFromColdLoop = new AutoResetEvent(false);
    private bool _disposing = false;
    private bool _adding = false;

    private List<Subscriber> _subscribers = new List<Subscriber>();
    private List<Subscriber> _pendingSubscribers = new List<Subscriber>();

    public bool IsActive { get { return _allowed.WaitOne(0); } }

    public HiResScheduler()
    {
        _spinner = new Thread(DoSpin);
        _spinner.Start();
    }

    public void Start()
    {
        _allowed.Set();
    }

    public void Pause()
    {
        _allowed.Reset();
    }

    public void Enqueue(double delayMs, Action callback)
    {
        lock (_pendingSubscribers)
        {
            _pendingSubscribers.Add(new Subscriber(delayMs, callback));
            _adding = true;
            if (delayMs <= HOT_LOOP_TRESHOLD_MS * 2)
                _wakeFromColdLoop.Set();
        }
    }

    private void DoSpin(object obj)
    {
        var sw = new Stopwatch();
        sw.Start();
        var nextFire = null as Subscriber;
        while (!_disposing)
        {
            _allowed.WaitOne();
            if (nextFire != null && sw.Elapsed.TotalMilliseconds >= nextFire?.DelayMs)
            {
                var diff = sw.Elapsed.TotalMilliseconds;
                sw.Restart();

                foreach (var item in _subscribers)
                    item.DelayMs -= diff;

                foreach (var item in _subscribers.Where(p => p.DelayMs <= 0).ToList())
                {
                    item.Callback?.Invoke();
                    _subscribers.Remove(item);
                }
                nextFire = _subscribers.FirstOrDefault();
            }

            if (_adding)
                lock (_pendingSubscribers)
                {
                    _subscribers.AddRange(_pendingSubscribers);
                    _pendingSubscribers.Clear();
                    _subscribers.Sort();
                    _adding = false;
                    nextFire = _subscribers.FirstOrDefault();
                }

            if (nextFire == null || nextFire.DelayMs > HOT_LOOP_TRESHOLD_MS)
                _wakeFromColdLoop.WaitOne(1);
            else
                _wakeFromColdLoop.WaitOne(0);
        }
    }

    public void Dispose()
    {
        _disposing = true;
    }
}
Vitaly
  • 813
  • 10
  • 17
  • How do we use this as a regular implementation of IScheduler? I want this for System.Reactive. I need to produce ticks at around 60 Hz for Observable.Interval. – SuperJMN Apr 03 '22 at 21:58
  • 1
    No idea, man. Give it a try with some creativity in mind, the thing can produce ticks at any rate, it's up to you to harness these and send to wherever – Vitaly Apr 05 '22 at 15:02
0

The previous example doesn't work unless the frequency is in milliseconds; the perf timer frequency is rarely in ms.

private static Int64 m_iPerfFrequency = -1;

public static double GetPerfCounter()
{
    // see if we need to get the frequency
    if (m_iPerfFrequency < 0)
    {
        if (QueryPerformanceFrequency(out m_iPerfFrequency) == 0)
        {
            return 0.0;
        }
    }

    Int64 iCount = 0;
    if (QueryPerformanceCounter(out iCount) == 0)
    {
        return 0.0;
    }

    return (double)iCount / (double)m_iPerfFrequency;
}

[DllImport("kernel32.dll", SetLastError = true)]
public static extern int QueryPerformanceCounter(out Int64 iCount);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern int QueryPerformanceFrequency(out Int64 iFrequency);

This returns the perf counter in seconds. The reason you'd use the perf timer is to share a timer with legacy C++ code, or get a more precise timer than the C# StopWatch class.

Pavel P
  • 15,789
  • 11
  • 79
  • 128
0

The system clock "ticks" at a constant rate. In order to raise the accuracy of timer dependent function*s, call **timeGetDevCaps* which determines the minimum supported timer resolution. Then call timeBeginPeriod setting the timer resolution to its minimum.

Caution: By invoking timeBeginPeriod, other timer dependent function can be significantly affected such as the system clock, system power usage, and the scheduler. Thus start your application with timeBeginPeriod and end it with timeEndPeriod

Lorenz Lo Sauer
  • 23,698
  • 16
  • 85
  • 87
0

You could use QueryPerformanceCounter() and QueryPerformanceTimer() as outlined in this article.

double-beep
  • 5,031
  • 17
  • 33
  • 41
Razzupaltuff
  • 2,250
  • 2
  • 21
  • 37