Skip to content

Updated to NET 6.0 and fixed Initialization. #2

@gmgallo

Description

@gmgallo

Hi @MikeCodesDotNET
This is a nice little Multimedia timer wrapper. As it was mentioned in other post the Initialization has some flaws that was easy to fix: period was already initialized by the Interval property, and Resolution must be set with Capabilities.PeriodMin. Here is the snippet with the correction needed:

        private void Initialize()
        {
            mode = TimerMode.Periodic;
  //          period << already initialized by the Interval property
            Resolution = TimeSpan.FromMilliseconds(Capabilities.PeriodMin);
            IsRunning = false;
            timeProcPeriodic = new TimeProc(PeriodicEventCallback);
            timeProcOneShot = new TimeProc(OneShotEventCallback);
            tickRaiser = new TickDelegate(OnTick);
        }

In my project I need a very precise timer in the order of the 10ms, so I went ahead and modified the test program to measure the actual accuracy and repeatability of this MM Timer using the highly accurate Stopwatch from System.Diagnostics. Here is the modified test program, and below some test results in my intel i7 machine.

using System;
using MultimediaTimer;
using System.Diagnostics;
using System.Threading.Tasks;

namespace DemoApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Multimedia Timer Test\n\n");

            var timer = new Timer();

            Console.WriteLine($"Timer resolution: {timer.Resolution} \nMin period: {timer.MinPeriod}ms \n" +
                $"Max period: {timer.MaxPeriod}ms\nStopwatch Frequency: {Stopwatch.Frequency/1000}KHz");

            Console.WriteLine("\nType PERIOD in milliseconds<Enter>");

            string line = Console.ReadLine();


            double period;

            if (!double.TryParse(line, out period) ) 
            { 
                Console.WriteLine($"Not a valid period: {line}"); return; 
            }

            Interval = period;

            timer = new(period); 
            timer.Elapsed += Timer_Tick;
            timer.Resolution = TimeSpan.FromMilliseconds(2);

            sw = Stopwatch.StartNew();
            timer.Start();

            Task.Delay((int)period*capacity + 1000).Wait();

            timer.Stop();                   
    
            PrintStats();   
        }

        static double Interval;
        static Stopwatch sw; 
        static int capacity = 1000;
        static int index = 0;

        static double[] Q = new double[capacity]; // can't use generics Queue in static fields.

        private static void Timer_Tick(object sender, EventArgs e)
        {
            double t = 1000.0D * sw.ElapsedTicks / Stopwatch.Frequency;

            Q[index++ % capacity] = t;

            Console.WriteLine($"Timer Ticked: {DateTime.Now.ToString("hh:mm:ss.fff")} - Stopwatch: {t: 0.00}");
            sw.Restart();
        }

        static void PrintStats()
        {
            double Max = double.MinValue;
            double Min = double.MaxValue;
            double Avg = 0;
            double cnt = Q.Length;  // assume that we have full Queue.

            foreach (var t in Q) 
            {
                if (t > Max)
                    Max = t;
                if (t < Min)
                    Min = t;

                Avg +=  t / cnt;
            }

            double ss = 0;

            foreach (var t in Q)
            {
                var s = t - Avg;
                ss += s * s;
            }
             
            double Std = Math.Sqrt(ss/cnt);

            Console.WriteLine($"Interval     : {Interval}");
            Console.WriteLine($"Samples      : {cnt}");
            Console.WriteLine($"Max value    : {Max:0.000}");
            Console.WriteLine($"Min value    : {Min:0.000}");
            Console.WriteLine($"Value Spread : {Max-Min:0.000}");
            Console.WriteLine($"Average      : {Avg:0.000}");
            Console.WriteLine($"Std Deviation: {Std:0.000}");
        }
    }
}

The program asks the user for a time interval and then waits for the necessary time to fill the 1000 samples array, then prints the statistics. Here are my results for a 100, 10, and 5 ms itervals.

Interval     : 100
Samples      : 1000
Max value    : 100.924
Min value    : 98.694
Value Spread : 2.230
Average      : 99.785
Std Deviation: 0.424

Interval     : 10
Samples      : 1000
Max value    : 10.936
Min value    : 7.279
Value Spread : 3.657
Average      : 9.770
Std Deviation: 0.489

Interval     : 5
Samples      : 1000
Max value    : 6.363
Min value    : 2.412
Value Spread : 3.951
Average      : 4.781
Std Deviation: 0.461

Unfortunately, it won't work for me with a variation of more than 3ms @ 10ms interval, it turns out that is not any better than the .NET System.Timers.Timer which also runs in the thread pool and has variations in the same order of magnitude. This Multimedia timer is using deprecated Win32 interfaces. It would be good to see if using the new Multimedia Class Scheduler Service a higher accuracy could be achieved, but that is left as an exercise for the reader, that is another way to say that I don't have time to doit right now :-).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions