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

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

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

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

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

Ответов (6)

Решение

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

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

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

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

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

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

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

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

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

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

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

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