TimeBeginPeriod / TimeEndPeriod Sequence

Viewed 459

I've two Windows .NET libraries A and B. Library A has TimeBeginPeriod(1) and TimeEndPeriod(1) while Library B has TimeBeginPeriod(5) and TimeEndPeriod(5). I've notice lately (after their behavor change) that when the Library B will do TimeEndPeriod(5) Library A looses TimeBeginPeriod(1).

Can you have different time periods within the same application without the one affect the other? I mean I don't mind if the (5) will be affected by (1) but it is crucial that end(5) will not affect the begin(1)!

LibA TimeBeginPeriod(1)
.... LibB TimeBeginPeriod(5)
.... LibB TimeEndPeriod(5)    <- Does this cancel TimeBeginPeriod(1)?
LibA TimeEndPeriod(1)

[Update]

Test Code

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

namespace ConsoleApp1
{
    class Program
    {
        static long freq;

        static void Main(string[] args)
        {
            QueryPerformanceFrequency(out freq);

            Console.WriteLine("Begin");
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());

            TimeBeginPeriod(1);
            Console.WriteLine("TimeBeginPeriod(1)");
            Thread.Sleep(5000);

            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());

            TimeBeginPeriod(15);
            Console.WriteLine("TimeBeginPeriod(15)");
            Thread.Sleep(5000);

            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());

            Console.WriteLine("TimeEndPeriod(15)");
            TimeEndPeriod(15);
            Thread.Sleep(5000);

            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());

            TimeEndPeriod(1);
            Console.WriteLine("TimeEndPeriod(1)");


            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine("End");
        }

        private static string CalcSleep1()
        {
            QueryPerformanceCounter(out long ticks1);
            Thread.Sleep(1);
            QueryPerformanceCounter(out long ticks2);
            return (new TimeSpan((long)(1000 * 10000 * (ticks2 - ticks1) / (double)freq))).ToString();
        }


        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

        [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)]
        public static extern uint TimeBeginPeriod(uint uMilliseconds);

        [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", SetLastError = true)]
        public static extern uint TimeEndPeriod(uint uMilliseconds);

        [DllImport("winmm.dll", ExactSpelling = true)]
        private static extern int timeGetDevCaps(ref TIMECAPS ptc, int cbtc);

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

            internal int wPeriodMax;
        }
    }
}

Output

Begin
00:00:00.0130730
00:00:00.0154164
TimeBeginPeriod(1)
00:00:00.0019935
00:00:00.0019148
00:00:00.0019145
00:00:00.0019203
00:00:00.0023354
TimeBeginPeriod(15)
00:00:00.0019916
00:00:00.0018920
00:00:00.0019412
00:00:00.0019411
00:00:00.0019336
TimeEndPeriod(15)
00:00:00.0019918
00:00:00.0019300
00:00:00.0019671
00:00:00.0019524
00:00:00.0019399
TimeEndPeriod(1)
00:00:00.0152576
00:00:00.0148129
End

It seems that the in-between TimeBeginPeriod(15) / TimeEndPeriod(15) does not affect TimeBeginPeriod(1). Except if that happens only on different assemblies (didn't tested yet)

1 Answers

By coding my application's logic with a smaller example I was able to reproduce and identify a serious Windows bug. It happens when you load an assembly dynamically. You can download the Solution and/or Compiled here

Update: It happens on both static/dynamic. It has different behavior when the global timer changes. What my application was doing was the following which Windows doesn't like:

.... LibB TimeBeginPeriod(5)
LibA TimeBeginPeriod(1)
.... LibB TimeEndPeriod(5)    <- This affects TimeBeginPeriod(1) goes back to 15ms - and it happens when the global timer is already to 1ms? - 
LibA TimeEndPeriod(1)

I would bet this issue affects a number of applications out there, as they don't really know what the underlying libraries doing with the Time periods.

(Copy the Class12 dll to the Console application output)

Class11

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

namespace ClassLibrary1
{
    public abstract class ClassAbstract
    {
        public abstract void StartSample(uint ms);
        public abstract void EndSample(uint ms);
        public abstract void PrintSample();
    }

    public class Class11
    {
        static long freq;

        public Class11()
        {
            QueryPerformanceFrequency(out freq);
        }

        private static string CalcSleep1()
        {
            QueryPerformanceCounter(out long ticks1);
            Thread.Sleep(1);
            QueryPerformanceCounter(out long ticks2);
            return (new TimeSpan((long)(1000 * 10000 * (ticks2 - ticks1) / (double)freq))).ToString();
        }

        public void StartSample(uint ms)
        {
            TimeBeginPeriod(ms);
            Console.WriteLine($"TimeBeginPeriod({ms})");

            PrintSample();
        }

        public void EndSample(uint ms)
        {
            Console.WriteLine($"TimeEndPeriod({ms})");
            TimeEndPeriod(ms);
        }

        public void PrintSample()
        {
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
        }


        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

        [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)]
        public static extern uint TimeBeginPeriod(uint uMilliseconds);

        [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", SetLastError = true)]
        public static extern uint TimeEndPeriod(uint uMilliseconds);
    }
}

Class12

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

using ClassLibrary1;

namespace ClassLibrary2
{
    public class Class12 : ClassAbstract
    {
        static long freq;

        public Class12()
        {
            QueryPerformanceFrequency(out freq);
        }

        private static string CalcSleep1()
        {
            QueryPerformanceCounter(out long ticks1);
            Thread.Sleep(1);
            QueryPerformanceCounter(out long ticks2);
            return (new TimeSpan((long)(1000 * 10000 * (ticks2 - ticks1) / (double)freq))).ToString();
        }

        public override void StartSample(uint ms)
        {
            TimeBeginPeriod(ms);
            Console.WriteLine($"TimeBeginPeriod({ms})");

            PrintSample();
        }

        public override void EndSample(uint ms)
        {
            Console.WriteLine($"TimeEndPeriod({ms})");
            TimeEndPeriod(ms);
        }

        public override void PrintSample()
        {
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
            Console.WriteLine(CalcSleep1());
        }


        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

        [DllImport("Kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

        [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)]
        public static extern uint TimeBeginPeriod(uint uMilliseconds);

        [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", SetLastError = true)]
        public static extern uint TimeEndPeriod(uint uMilliseconds);
    }
}

ConsoleApp

using System;
using System.IO;
using System.Reflection;

using ClassLibrary1;

namespace ConsoleApp1
{
    class Program
    {
        static Class11 class11;
        static ClassAbstract class12;

        static void Main(string[] args)
        {
            class11 = new Class11();

            var assembly =  Assembly.LoadFrom(Path.GetFullPath(@"ClassLibrary2.dll"));
            ClassAbstract class12 = (ClassAbstract) Activator.CreateInstance(assembly.GetTypes()[0]);

            // Behavior changes while System global time period changes
            class12.StartSample(5); // Bug here we get 1ms instead of 5ms
            class11.StartSample(1);
            class12.EndSample(5);
            class11.PrintSample(); // Bug Here we get 15 ms period instead of 1ms
            class11.EndSample(1);
        }
    }
}
Related