Поднять событие в интервале с высоким разрешением / таймер - PullRequest
15 голосов
/ 18 ноября 2010

Я хочу использовать таймер с максимально возможным разрешением, используя c #. Например, я хочу поднять событие каждые 11 тиков (я слышал, что тик - самый высокий возможный счетчик в ПК). Я попробовал таймер и обнаружил, что минимальное истекшее время в миллисекундах. Я посмотрел на секундомер, но секундомер не вызывает события.

Спасибо.

Ответы [ 3 ]

18 голосов
/ 13 декабря 2010

Использование мультимедийного таймера должно дать вам около 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, но, пожалуйста, не делайте этого.

14 голосов
/ 18 ноября 2010

Прежде всего, вы должны понимать, что чрезвычайно трудно, если не невозможно, установить точную синхронизацию на компьютере из-за ограничений, выставляемых как аппаратным, так и программным обеспечением. Хорошая новость заключается в том, что такого родаточности редко требуется.Десять тиков - это безумно маленький промежуток времени.В этот интервал ЦП выполняет очень мало работы, и он никогда не будет статистически значимым.

Для справки, тактовая частота 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/)

4 голосов
/ 18 ноября 2010

Различные классы таймера используют большую степень детализации. Оба 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 циклов процессора: этого достаточно для выполнения нескольких десятков математических операций и, возможно, для вызова одной функции.

...