Поднять событие в интервале с высоким разрешением / таймер
Я хочу использовать таймер с максимально возможным разрешением, используя C#. Например, я хочу вызывать событие каждые 11 тиков (я слышал, что тик - самый высокий возможный счетчик в ПК). Я попробовал таймер и обнаружил, что минимальное истекшее время в миллисекундах. Я посмотрел на секундомер, но секундомер не вызывает событий.
Благодарю.
3 ответа
Использование мультимедийного таймера должно дать вам около 1000 событий в секунду. Этот код должен помочь вам в пути.
public delegate void TimerEventHandler(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);
/// <summary>
/// A multi media timer with millisecond precision
/// </summary>
/// <param name="msDelay">One event every msDelay milliseconds</param>
/// <param name="msResolution">Timer precision indication (lower value is more precise but resource unfriendly)</param>
/// <param name="handler">delegate to start</param>
/// <param name="userCtx">callBack data </param>
/// <param name="eventType">one event or multiple events</param>
/// <remarks>Dont forget to call timeKillEvent!</remarks>
/// <returns>0 on failure or any other value as a timer id to use for timeKillEvent</returns>
[DllImport("winmm.dll", SetLastError = true,EntryPoint="timeSetEvent")]
static extern UInt32 timeSetEvent(UInt32 msDelay, UInt32 msResolution, TimerEventHandler handler, ref UInt32 userCtx, UInt32 eventType);
/// <summary>
/// The multi media timer stop function
/// </summary>
/// <param name="uTimerID">timer id from timeSetEvent</param>
/// <remarks>This function stops the timer</remarks>
[DllImport("winmm.dll", SetLastError = true)]
static extern void timeKillEvent( UInt32 uTimerID );
Остановите эти таймеры после запуска. Они довольно тяжелые в вашей системе *. Перехватывайте все исключения и не позволяйте им избежать вашего обработчика событий.
* Запуск более 5 таймеров серьезно замедлит работу большинства систем! Выполните как можно меньше кода в обработчиках событий и убедитесь, что исполняемый код быстрее 1 миллисекунды или столкнулся с серьезными проблемами. Я начал делегировать каждые 10-50 тиков, чтобы увеличить отображение метки.
Нормальный переключатель потока, который происходит на Thread.Sleep
оставит один поток-слот свободным от вашего кода и займет около 40 миллисекунд. Вы также можете увеличить частоту переключения потоков с помощью некоторых вызовов ядра NT, но, пожалуйста, не делайте этого.
Прежде всего, вы должны понимать, что очень трудно, если не невозможно, точно установить время на компьютере из-за ограничений, связанных как с аппаратным, так и с программным обеспечением. Хорошей новостью является то, что такая точность редко необходима. Десять тиков - это безумно мало времени. В этот промежуток времени CPU выполняет очень мало работы, и она никогда не будет статистически значимой.
Для справки, часы Windows имеют точность около 10 миллисекунд (меньше в более ранних версиях). Оборачиваем ваш код звонками на DateTime.UtcNow
не собирается делать лучше, чем это.
В своем вопросе вы говорите о желании "поднять событие". Проблема заключается в том, что единственным типом объекта учета времени, который вызывает событие через определенные промежутки времени, является Timer
объект. Он доступен в 3 разных вариантах в.NET Framework (System.Timers.Timer
, System.Threading.Timer
, а также System.Windows.Forms.Timer
), каждый из которых имеет свои уникальные сценарии использования и относительные причуды, но ни один из них не гарантирует точность, близкую к тому, что вы просите. Они даже не предназначены для этого, и при этом не существует каких-либо эквивалентных функций, предоставляемых Windows API, которые обеспечат этот тип точности.
Причина, по которой я спросил, почему вы хотели это сделать, и пытаетесь ли вы провести сравнительный анализ, заключается в том, что это меняет всю игру..NET Framework (начиная с версии 2.0) предоставляет Stopwatch
объект, который специально предназначен для точного измерения прошедшего времени в такой ситуации, как сравнительный анализ или профилирование производительности. Stopwatch
просто оборачивает функции Windows API QueryPerformanceFrequency
а также QueryPerformanceCounter
(что должно подтвердить мое предложение относительно его предполагаемого использования). Мы использовали P/Invoke для этих функций, чтобы получить доступ к этому типу функциональности в более ранних версиях Framework, но теперь это удобно встроено. Если вам нужен таймер с относительно высоким разрешением для бенчмаркинга, Stopwatch
это ваша лучшая ставка. Теоретически, он может предоставить вам субмикросекундную синхронизацию.
Но это не без проблем. Он не вызывает никаких событий, поэтому, если ваш текущий дизайн основан на обработке событий, вам придется переосмыслить его. И это не гарантирует, что он будет совершенно точным. Конечно, оно может иметь максимально возможное разрешение с учетом аппаратных ограничений, но это не значит, что оно обязательно будет соответствовать вашим заявленным требованиям. Например, это может быть ненадежно в многопроцессорной системе, где Start
а также Stop
должны быть выполнены на том же процессоре. Это не должно иметь значения, но это имеет значение. Он также может быть ненадежным на процессорах, которые могут снижать тактовую частоту вверх и вниз. И смею даже упомянуть, что призыв к QueryPerformanceCounter
Само по себе это займет некоторое время - около 5 микросекунд даже на современном процессоре с тактовой частотой 2 ГГц, что не позволяет вам на самом деле достичь той субмикросекундной синхронизации, которая звучит хорошо в теории. С другой стороны, любой разумный профилировщик кода посчитал бы это время незначительным, потому что, ну, это так.
(См. Также: http://www.devsource.com/c/a/Techniques/High-Performance-Timing-under-Windows/2/)
Различные классы таймера используют большую степень детализации. И Threading.Timer, и Timers.Timer используют 1/64 секунды, что составляет 15,625 миллисекунд.
Если "тик", на который вы ссылаетесь, это тик 100 наносекунд, используемый классом DateTime, классом TimeSpan и выводимый секундомером, то длина 11 тиков, о которой вы спрашиваете, составляет 1100 наносекунд или 1,1 микросекунды. Насколько я знаю, нет встроенного таймера, который даст вам такое разрешение. Если вы действительно хотите, чтобы событие происходило каждые 1,1 микросекунды, вам придется удалить идею "таймера" и вместо этого думать с точки зрения короткой задержки. Сделайте поток высоким приоритетом и запустите ваше событие в цикле. Не вызывайте Thread.Sleep(), поскольку я считаю, что 1,1 микросекунды меньше, чем временной интервал системного планировщика. Вам нужно будет сделать петлю задержки вместо этого.
Кроме того, осознайте, что временной интервал, о котором вы спрашиваете, очень и очень мал. 1,1 микросекунды - это всего 2200 процессорных циклов на процессоре с частотой 2 ГГц. Немалое количество, но не так много времени, чтобы проделать большую работу. Если вы говорите 1 тик, который вы сказали в своем комментарии, это всего лишь 200 тактов процессора: это достаточно времени, чтобы выполнить несколько десятков математических операций и, возможно, вызвать одну функцию.