Поднять событие в интервале высокого разрешения/таймера
Я хочу использовать максимально возможный таймер разрешения с помощью С#. Например, я хочу поднять событие каждые 11 тиков (я слышал, что тик - это самый высокий счетчик в ПК). Я попробовал таймер и обнаружил, что минимальное время истечения в миллисекундах. посмотрел на секундомер, но секундомер не увеличивает события.
Спасибо.
Ответы
Ответ 1
Использование мультимедийного таймера должно дать вам около 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, но, пожалуйста, не делайте этого.
Ответ 2
Во-первых, вам нужно понять, что чрезвычайно сложно, если не невозможно, делать точные сроки на компьютере из-за ограничений, предоставляемых как аппаратным, так и программным обеспечением. Хорошая новость заключается в том, что это редко бывает необходима точность. Десять тиков - безумно небольшое количество времени. В этом интервале очень мало работы делается процессором, и это никогда не будет статистически значимым.
Для справки, часы Windows имеют точность около 10 миллисекунд (меньше в более ранних версиях). Завершение кода с помощью DateTime.UtcNow
не будет лучше.
В вашем вопросе вы говорите о желании "поднять событие". Проблема заключается в том, что единственным типом объекта, сохраняющего время, который вызывает событие через определенные промежутки времени, является объект Timer
. Он доступен в трех разных воплощениях в .NET Framework (System.Timers.Timer
, System.Threading.Timer
и System.Windows.Forms.Timer
), все из которых имеют свои уникальные сценарии использования и относительные причуды, но ни одна из них не гарантирует точность в любом месте, близком к тому, что вы просите. Они даже не предназначены для этого, и нет никаких эквивалентных функций, открытых API Windows, которые будут предоставлять этот тип точности.
Причина, по которой я спросил, почему вы хотите это сделать, и если вы пытаетесь провести бенчмаркинг, это потому, что это изменяет всю игру..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/)
Ответ 3
Различные классы таймеров используют большую степень детализации. Оба 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 процессорных циклов: это достаточно времени, чтобы выполнить несколько десятков математических операций и, возможно, вызвать одну функцию.