Получение точных тиков от таймера в С#

Я пытаюсь перестроить старое приложение метронома, которое изначально было написано с использованием MFC на С++, написанное на .NET с использованием С#. Одна из проблем, с которыми я сталкиваюсь, заключается в том, чтобы таймер был достаточно "тик".

Например, если принять простой BPM (удары в минуту) 120, таймер должен указывать каждые 0,5 секунды (или 500 миллисекунд). Однако использование этого в качестве основы для тиков не совсем точно, так как .NET гарантирует, что ваш таймер не будет отмечен до истечения прошедшего времени.

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

Итак, каков наилучший способ получить точные клещи? Я знаю, что есть больше таймеров, чем таймер окон, который легко доступен в Visual Studio, но я не очень-то знаком с ними.

Ответы

Ответ 1

В .NET есть три класса таймера под названием "Таймер". Похоже, что вы используете Windows Forms, но на самом деле вы можете найти класс System.Threading.Timer более полезным - но будьте осторожны, потому что он обращается к потоку пула, поэтому вы не можете напрямую взаимодействовать с вашей формой из обратный вызов.

Другим подходом может быть p/invoke для мультимедийных таймеров Win32 - timeGetTime, timeSetPeriod и т.д.

Быстрый Google нашел это, что может быть полезно http://www.codeproject.com/KB/miscctrl/lescsmultimediatimer.aspx

"Мультимедиа" (таймер) - это ключевое слово для поиска в этом контексте.

Ответ 2

Что такое приложение на С++? Вы всегда можете использовать одну и ту же вещь или поместить код таймера из С++ в класс С++/CLI.

Ответ 3

У меня возникла эта проблема при разработке недавнего проекта регистрации данных. Проблема с таймерами .NET(windows.forms, system.threading и system.timer) заключается в том, что они являются точными вплоть до 10 миллисекунд, что связано с планированием событий, встроенным в .NET. Я полагаю. (Я говорю о .NET 2 здесь). Это было неприемлемо для меня, поэтому мне пришлось использовать мультимедийный таймер (вам нужно импортировать dll). Я также написал класс-оболочку для всех таймеров, и поэтому вы можете переключаться между ними, если это необходимо, используя минимальные изменения кода. Посмотрите мой блог здесь: http://www.indigo79.net/archives/27

Ответ 4

Другая возможность заключается в том, что в WPF реализована ошибка DispatcherTimer (существует несоответствие между миллисекундами и тиками, вызывающее потенциальную неточность в зависимости от точного времени выполнения процесса), как показано ниже:

http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/DispatcherTimer.cs,143

class DispatcherTimer
{
    public TimeSpan Interval
    {
        set
        {
            ...
            _interval = value;
            // Notice below bug: ticks1 + milliseconds [Bug1]
            _dueTimeInTicks = Environment.TickCount + (int)_interval.TotalMilliseconds;
        }
    }
}

http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs

class Dispatcher
{
    private object UpdateWin32TimerFromDispatcherThread(object unused)
    {
        ...
        _dueTimeInTicks = timer._dueTimeInTicks;
        SetWin32Timer(_dueTimeInTicks);
    }

    private void SetWin32Timer(int dueTimeInTicks)
    {
        ...
        // Notice below bug: (ticks1 + milliseconds) - ticks2  [Bug2 - almost cancels Bug1, delta is mostly milliseconds not ticks]
        int delta = dueTimeInTicks - Environment.TickCount; 
        SafeNativeMethods.SetTimer( 
            new HandleRef(this, _window.Value.Handle),
            TIMERID_TIMERS,
            delta); // <-- [Bug3 - if delta is ticks, it should be divided by TimeSpan.TicksPerMillisecond = 10000]
    }
}

http://referencesource.microsoft.com/#WindowsBase/Shared/MS/Win32/SafeNativeMethodsCLR.cs,505

class SafeNativeMethodsPrivate
{
    ...
    [DllImport(ExternDll.User32, SetLastError = true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)]
    public static extern IntPtr SetTimer(HandleRef hWnd, int nIDEvent, int uElapse, NativeMethods.TimerProc lpTimerFunc);
}

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644906%28v=vs.85%29.aspx

uElapse [in]
Type: UINT
The time-out value, in milliseconds. // <-- milliseconds were needed eventually

Ответ 5

Классы таймера могут начать странно вести себя, когда код события "tick" таймера не завершен, к тому моменту, когда произойдет следующий "тик". Один из способов борьбы с этим - отключить таймер в начале события тика, а затем снова включить его в конце.

Однако этот подход не подходит в случаях, когда время выполнения кода "tick" не является допустимой ошибкой в ​​тактике галочки, поскольку таймер будет отключен (не считая) в течение этого времени.

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

Ответ 6

System.Windows.Forms.Timer ограничивается точностью 55 миллисекунд...