timeBeginPeriod не работает на процессоре Intel Comet Lake (i5 10400H) - PullRequest
0 голосов
/ 13 июля 2020

В моем приложении есть несколько операций, которые зависят от коротких таймеров. Используя приведенный ниже пример кода, у меня есть таймеры, срабатывающие каждые ~ 5 мс по мере необходимости.

На процессоре Intel i5 10400H тайминги отключены, а обратный вызов происходит через ~ 15 мс (или кратно 15). Использование инструмента ClockRes sysinternals показывает, что машина имеет разрешение системного таймера 15 мс даже при запуске после вызова timeBeginPeriod(1), сделанного в приведенном ниже коде.

Использование https://cms.lucashale.com/timer-resolution/ для установки разрешение до максимального поддерживаемого значения (0,5 мс) не меняет поведения примера кода.

Из того, что я вижу, машина использует таймер Invariant TS C acpi и заставляет его использовать HPET (с bcdedit /set useplatformclock true и перезагрузкой) не изменил поведения.

Я не вижу ничего в документации по ЦП или исправлений, которые могли бы это объяснить.

Я не знаю, где проблема заключается в том, и если это что-то, что можно исправить с моей стороны, какие-нибудь идеи?

Изменить: открытие этой программы ( DP C Latency Checker ) приводит к срабатыванию очереди таймера когда ожидается, поэтому это решаемо.

Пример кода:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (new TimePeriod(1))
                RunTimer();
        }

        public static void RunTimer()
        {
            var completionEvent = new ManualResetEvent(false);
            var stopwatch = Stopwatch.StartNew();
            var i = 0;
            var previous = 0L;
            using var x = TimerQueue.Default.CreateTimer((s) =>
            {
                if (i > 100)
                    completionEvent.Set();
                i++;
                var now = stopwatch.ElapsedMilliseconds;
                var gap = now - previous;
                previous = now;
                Console.WriteLine($"Gap: {gap}ms");
            }, "", 10, 5);
            completionEvent.WaitOne();
        }
    }

    public class TimerQueueTimer : IDisposable
    {
        private TimerQueue MyQueue;
        private TimerCallback Callback;
        private object UserState;
        private IntPtr Handle;

        internal TimerQueueTimer(
            TimerQueue queue,
            TimerCallback cb,
            object state,
            uint dueTime,
            uint period,
            TimerQueueTimerFlags flags)
        {
            MyQueue = queue;
            Callback = cb;
            UserState = state;
            bool rslt = TQTimerWin32.CreateTimerQueueTimer(
                out Handle,
                MyQueue.Handle,
                TimerCallback,
                IntPtr.Zero,
                dueTime,
                period,
                flags);
            if (!rslt)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(), "Error creating timer.");
            }
        }
        ~TimerQueueTimer()
        {
            Dispose(false);
        }
        public void Change(uint dueTime, uint period)
        {
            bool rslt = TQTimerWin32.ChangeTimerQueueTimer(MyQueue.Handle, ref Handle, dueTime, period);
            if (!rslt)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(), "Error changing timer.");
            }
        }
        private void TimerCallback(IntPtr state, bool bExpired)
        {
            Callback.Invoke(UserState);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private IntPtr completionEventHandle = new IntPtr(-1);
        public void Dispose(WaitHandle completionEvent)
        {
            completionEventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle();
            this.Dispose();
        }
        private bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                bool rslt = TQTimerWin32.DeleteTimerQueueTimer(MyQueue.Handle,
                    Handle, completionEventHandle);
                if (!rslt)
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error(), "Error deleting timer.");
                }
                disposed = true;
            }
        }
    }

    public class TimerQueue : IDisposable
    {
        public IntPtr Handle { get; private set; }
        public static TimerQueue Default { get; private set; }
        static TimerQueue()
        {
            Default = new TimerQueue(IntPtr.Zero);
        }
        private TimerQueue(IntPtr handle)
        {
            Handle = handle;
        }
        public TimerQueue()
        {
            Handle = TQTimerWin32.CreateTimerQueue();
            if (Handle == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error(), "Error creating timer queue.");
            }
        }
        ~TimerQueue()
        {
            Dispose(false);
        }
        public TimerQueueTimer CreateTimer(
            TimerCallback callback,
            object state,
            uint dueTime,
            uint period)
        {
            return CreateTimer(callback, state, dueTime, period, TimerQueueTimerFlags.ExecuteInPersistentThread);
        }

        public TimerQueueTimer CreateTimer(
            TimerCallback callback,
            object state,
            uint dueTime,
            uint period,
            TimerQueueTimerFlags flags)
        {
            return new TimerQueueTimer(this, callback, state, dueTime, period, flags);
        }

        private IntPtr CompletionEventHandle = new IntPtr(-1);

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(WaitHandle completionEvent)
        {
            CompletionEventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle();
            Dispose();
        }

        private bool Disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                if (Handle != IntPtr.Zero)
                {
                    bool rslt = TQTimerWin32.DeleteTimerQueueEx(Handle, CompletionEventHandle);
                    if (!rslt)
                    {
                        int err = Marshal.GetLastWin32Error();
                        throw new Win32Exception(err, "Error disposing timer queue");
                    }
                }
                Disposed = true;
            }
        }
    }

    public enum TimerQueueTimerFlags : uint
    {
        ExecuteDefault = 0x0000,
        ExecuteInTimerThread = 0x0020,
        ExecuteInIoThread = 0x0001,
        ExecuteInPersistentThread = 0x0080,
        ExecuteLongFunction = 0x0010,
        ExecuteOnlyOnce = 0x0008,
        TransferImpersonation = 0x0100,
    }

    public delegate void Win32WaitOrTimerCallback(
        IntPtr lpParam,
        [MarshalAs(UnmanagedType.U1)] bool bTimedOut);

    static public class TQTimerWin32
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public extern static IntPtr CreateTimerQueue();

        [DllImport("kernel32.dll", SetLastError = true)]
        public extern static bool DeleteTimerQueue(IntPtr timerQueue);

        [DllImport("kernel32.dll", SetLastError = true)]
        public extern static bool DeleteTimerQueueEx(IntPtr timerQueue, IntPtr completionEvent);

        [DllImport("kernel32.dll", SetLastError = true)]
        public extern static bool CreateTimerQueueTimer(
            out IntPtr newTimer,
            IntPtr timerQueue,
            Win32WaitOrTimerCallback callback,
            IntPtr userState,
            uint dueTime,
            uint period,
            TimerQueueTimerFlags flags);

        [DllImport("kernel32.dll", SetLastError = true)]
        public extern static bool ChangeTimerQueueTimer(
            IntPtr timerQueue,
            ref IntPtr timer,
            uint dueTime,
            uint period);

        [DllImport("kernel32.dll", SetLastError = true)]
        public extern static bool DeleteTimerQueueTimer(
            IntPtr timerQueue,
            IntPtr timer,
            IntPtr completionEvent);
    }

    public 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;
        }
    }
}

Ответы [ 2 ]

1 голос
/ 13 июля 2020

Похоже, это проблема с windows 10 2004 . Я предполагаю, что это не имеет ничего общего с процессором / материнской платой.

Возможный обходной путь может заключаться в использовании секундомера и spinwait в потоке . Это было бы нецелесообразно для обычных потребительских приложений, поскольку это потребовало бы полного потока, но может быть осуществимо, если у вас есть полный контроль над системой.

0 голосов
/ 23 июля 2020

Возникла та же проблема, я использую CreateTimerQueueTimer. Все еще работает timeSetEvent. Вы потеряете некоторую точность, поскольку это целые миллисекунды, но это лучше, чем ничего.

...