Почему таймеры .NET ограничены разрешением 15 мс?

Обратите внимание, что я спрашиваю о том, что вызовет функцию обратного вызова чаще, чем раз в 15 мс, используя что-то вроде System.Threading.Timer . Я не спрашиваю, как точно время fragmentировать код, используя что-то вроде System.Diagnostics.Stopwatch или даже QueryPerformanceCounter .

Кроме того, я прочитал связанные вопросы:

Точный таймер Windows? System.Timers.Timer () ограничено 15 мс

Таймер высокого разрешения в .NET.

Ни один из них не дает полезного ответа на мой вопрос.

Кроме того, рекомендуемая статья MSDN « Внедрение постоянно обновляемого поставщика времени с высоким разрешением для Windows» относится к выбору времени, а не к непрерывному streamу тиков.

С этим сказано. , ,

Там есть много плохой информации о объектах таймера .NET. Например, System.Timers.Timer объявляется как «высокопроизводительный таймер, оптимизированный для серверных приложений». И System.Threading.Timer как-то считается гражданином второго classа. Обычная мудрость заключается в том, что System.Threading.Timer представляет собой оболочку вокруг таймеров очереди таймеров Windows и что System.Timers.Timer – это нечто совсем другое.

Реальность сильно отличается. System.Timers.Timer – это всего лишь тонкая shell компонента вокруг System.Threading.Timer (просто используйте Reflector или ILDASM, чтобы заглянуть внутрь System.Timers.Timer и вы увидите ссылку на System.Threading.Timer ) и имеет некоторый код который обеспечит автоматическую синхронизацию streamов, поэтому вам не нужно это делать.

System.Threading.Timer , как оказалось, не является оболочкой для таймеров очереди таймера. По крайней мере, не в среде исполнения 2.0, которая использовалась с .NET 2.0 через .NET 3.5. Несколько минут с CLI общего источника показывают, что среда выполнения выполняет свою собственную очередь таймера, которая похожа на таймеры очереди таймера, но на самом деле не вызывает функции Win32.

Похоже, среда выполнения .NET 4.0 также реализует собственную очередь таймера. Моя тестовая программа (см. Ниже) дает аналогичные результаты в .NET 4.0, как и в .NET 3.5. Я создал свою собственную управляемую оболочку для таймеров очереди таймеров и доказал, что могу получить разрешение 1 мс (с неплохой точностью), поэтому считаю маловероятным, что я неправильно читаю источник CLI.

У меня есть два вопроса:

Во-первых, что приводит к тому, что выполнение очереди таймера выполняется слишком медленно? Я не могу получить разрешение более 15 мс, и точность, кажется, находится в диапазоне от -1 до +30 мс. То есть, если я попрошу 24 мс, я получу тики где-нибудь от 23 до 54 мс друг от друга. Полагаю, я мог бы потратить еще некоторое время с источником CLI, чтобы отследить ответ, но подумал, что кто-то здесь может это знать.

Во-вторых, и я понимаю, что это труднее ответить, почему бы не использовать таймеры очереди таймеров? Я понимаю, что .NET 1.x должен был работать на Win9x, у которого не было этих API, но они существовали с Windows 2000, что, если я правильно помню, было минимальным требованием для .NET 2.0. Это потому, что CLI пришлось запускать на не-Windows-боксах?

Моя программа тестирования таймеров:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; namespace TimerTest { class Program { const int TickFrequency = 5; const int TestDuration = 15000; // 15 seconds static void Main(string[] args) { // Create a list to hold the tick times // The list is pre-allocated to prevent list resizing // from slowing down the test. List tickTimes = new List(2 * TestDuration / TickFrequency); // Start a stopwatch so we can keep track of how long this takes. Stopwatch Elapsed = Stopwatch.StartNew(); // Create a timer that saves the elapsed time at each tick Timer ticker = new Timer((s) => { tickTimes.Add(Elapsed.ElapsedMilliseconds); }, null, 0, TickFrequency); // Wait for the test to complete Thread.Sleep(TestDuration); // Destroy the timer and stop the stopwatch ticker.Dispose(); Elapsed.Stop(); // Now let's analyze the results Console.WriteLine("{0:N0} ticks in {1:N0} milliseconds", tickTimes.Count, Elapsed.ElapsedMilliseconds); Console.WriteLine("Average tick frequency = {0:N2} ms", (double)Elapsed.ElapsedMilliseconds / tickTimes.Count); // Compute min and max deviation from requested frequency double minDiff = double.MaxValue; double maxDiff = double.MinValue; for (int i = 1; i < tickTimes.Count; ++i) { double diff = (tickTimes[i] - tickTimes[i - 1]) - TickFrequency; minDiff = Math.Min(diff, minDiff); maxDiff = Math.Max(diff, maxDiff); } Console.WriteLine("min diff = {0:N4} ms", minDiff); Console.WriteLine("max diff = {0:N4} ms", maxDiff); Console.WriteLine("Test complete. Press Enter."); Console.ReadLine(); } } } 

Возможно, этот документ здесь немного объясняет. Это немного сухо, поэтому я только быстро его просмотрел 🙂

Цитируя введение:

Разрешение системного таймера определяет, как часто Windows выполняет два основных действия:

  • Обновите счетчик таймера, если истек полный тик.
  • Проверьте, истек ли объект запланированного таймера.

Таймер-тик – это понятие прошедшего времени, которое Windows использует для отслеживания времени суток и квантового времени streamа. По умолчанию таймер прерывания и таймер совпадают, но Windows или приложение могут изменять период прерывания тактового сигнала.

По умолчанию разрешение по умолчанию для Windows 7 составляет 15,6 миллисекунды (мс). В некоторых приложениях это сокращается до 1 мс, что сокращает время работы батареи на мобильных системах на целых 25 процентов.

Первоначально из: Таймеры, Разрешение Таймера и Разработка эффективного кода (docx).

Разрешение таймера задается пульсом системы. Обычно это значение равно 64 бит / с, что составляет 15.625 мс. Однако есть способы изменить эти системные настройки для достижения разрешения таймера до 1 мс или даже до 0,5 мс на новых платформах:

1. Переход на 1 мс с помощью интерфейса мультимедийного таймера:

Интерфейс мультимедийного таймера способен обеспечить разрешение до 1 мс. См. Раздел « Мультимедийные таймеры» (MSDN), « Получение и настройка разрешения таймера» (MSDN), и этот ответ для получения дополнительной информации о timeBeginPeriod . Примечание. Не забудьте вызвать timeEndPeriod, чтобы вернуться к заданному по умолчанию таймеру.

Как сделать:

 #define TARGET_RESOLUTION 1 // 1-millisecond target resolution TIMECAPS tc; UINT wTimerRes; if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) { // Error; application can't continue. } wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax); timeBeginPeriod(wTimerRes); // do your stuff here at approx. 1 ms timer resolution timeEndPeriod(wTimerRes); 

Примечание. Эта процедура также применима к другим процессам, и полученная резолюция применяется в целом. Самое высокое разрешение, запрошенное любым процессом, будет активным, помните о последствиях.

2. Переход к разрешению 0,5 мс:

Вы можете получить разрешение 0,5 мс с помощью скрытого API NtSetTimerResolution() . NtSetTimerResolution экспортируется родной библиотекой NTDLL.DLL для Windows NT. См. Как установить разрешение таймера на 0,5 мс? на MSDN. Тем не менее, истинное достижимое разрешение определяется базовым оборудованием. Современное оборудование поддерживает разрешение 0,5 мс. Более подробная информация содержится во внутренних таймерах с высоким разрешением Windows NT . Поддерживаемые разрешения могут быть получены вызовом NtQueryTimerResolution ().

Как сделать:

 #define STATUS_SUCCESS 0 #define STATUS_TIMER_RESOLUTION_NOT_SET 0xC0000245 // after loading NtSetTimerResolution from ntdll.dll: // The requested resolution in 100 ns units: ULONG DesiredResolution = 5000; // Note: The supported resolutions can be obtained by a call to NtQueryTimerResolution() ULONG CurrentResolution = 0; // 1. Requesting a higher resolution // Note: This call is similar to timeBeginPeriod. // However, it to to specify the resolution in 100 ns units. if (NtSetTimerResolution(DesiredResolution ,TRUE,&CurrentResolution) != STATUS_SUCCESS) { // The call has failed } printf("CurrentResolution [100 ns units]: %d\n",CurrentResolution); // this will show 5000 on more modern platforms (0.5ms!) // do your stuff here at 0.5 ms timer resolution // 2. Releasing the requested resolution // Note: This call is similar to timeEndPeriod switch (NtSetTimerResolution(DesiredResolution ,FALSE,&CurrentResolution) { case STATUS_SUCCESS: printf("The current resolution has returned to %d [100 ns units]\n",CurrentResolution); break; case STATUS_TIMER_RESOLUTION_NOT_SET: printf("The requested resolution was not set\n"); // the resolution can only return to a previous value by means of FALSE // when the current resolution was set by this application break; default: // The call has failed } 

Примечание. Функциональность NtSetTImerResolution в основном отображается на функции timeBeginPeriod и timeEndPeriod с помощью Set значений bool (см. timeEndPeriod Таймеры высокого разрешения для Windows NT» для получения более подробной информации о схеме и всех ее последствиях). Однако мультимедийный пакет ограничивает степень детализации до миллисекунд, а NtSetTimerResolution позволяет установить значения в миллисекундах.

  • Как измерить временной интервал в C?
  • Печать «привет мир» каждые X секунд
  • Лучший таймер для использования в службе Windows
  • .NET, событие каждую минуту (в минуту). Является ли таймер лучшим вариантом?
  • Являются ли таймеры и петли в .Net точными?
  • System.Timers.Timer / Threading.Timer vs Thread with WhileLoop + Thread.Sleep для периодических задач
  • Как измерить, как долго работает функция?
  • Как вызвать метод пользовательского интерфейса из другого streamа
  • Как запустить метод каждые X секунд
  • Как написать таймер в Objective-C?
  • Счетчик jQuery для подсчета до целевого номера
  • Interesting Posts

    Как получить код ответа HTTP с помощью Selenium WebDriver

    1.4 ГБ электронной почты, Thunderbird все сообщения электронной почты пустые при открытии

    Как очистить текст всех текстовых полей в форме?

    Добавление jQuery в PrimeFaces приводит к тому, что Uncaught TypeError по всему месту

    Как изменить параметры загрузки в Windows 8?

    FileNotFoundException при получении объекта InputStream из HttpURLConnection

    Включить сериализацию HAL в Spring Boot для пользовательского метода controllerа

    Контекст веб-приложения / контекст корневого приложения и настройка менеджера транзакций

    Где я могу найти подробное сравнение фреймворков Java XML?

    Как я могу начать новую активность Android с помощью строки?

    Как получить доступ к samba на гостевой Linux за VirtualBox NAT?

    Создание загрузочного компакт-диска из ISO

    Размер сбора счетчика Hibernate без инициализации

    Как использовать getJSON, отправляя данные с помощью метода post?

    Как локализовать мое приложение с Xcode 4?

    Давайте будем гением компьютера.