Почему таймеры .NET ограничены разрешением 15 мс? - PullRequest
60 голосов
/ 19 сентября 2010

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

Кроме того, я прочитал соответствующие вопросы:

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

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

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

Кроме того, рекомендуемая статья MSDN, Реализация постоянно обновляющегося поставщика времени с высоким разрешением для Windows , посвящена синхронизации времени, а не обеспечению непрерывного потока тиков.

С этим сказал. , .

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

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

System.Threading.Timer, как выясняется, не является оболочкой для таймеров очереди таймера. По крайней мере, не во время выполнения 2.0, которое использовалось от .NET 2.0 до .NET 3.5. Несколько минут с CLI Shared Source показывают, что среда выполнения реализует свою собственную очередь таймера, которая похожа на таймеры очереди таймера, но фактически никогда не вызывает функции 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<double> tickTimes = new List<double>(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();
        }
    }
}

Ответы [ 3 ]

28 голосов
/ 19 сентября 2010

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

Цитирование вступления:

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

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

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

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

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

14 голосов
/ 04 апреля 2014

Разрешение таймера задается системным пульсом.Обычно это значение по умолчанию составляет 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 экспортируется родной библиотекой Windows NT NTDLL.DLL.См. Как установить разрешение таймера на 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 с использованием значения bool Set (см. Внутри таймеров высокого разрешения Windows NT для получения дополнительной информации о схеме и всех ее последствиях).Однако мультимедийный пакет ограничивает гранулярность до миллисекунд, а NtSetTimerResolution позволяет устанавливать значения менее миллисекунды.

0 голосов
/ 27 сентября 2018

Все повторы здесь касаются разрешения системного таймера. Но .net таймеры не уважают его. Как заметил сам автор:

что среда выполнения реализует свою собственную очередь таймера, аналогичную Таймер очереди Таймеры, но на самом деле никогда не вызывает функции Win32.

И Ян указал в комментарий .

Итак, приведенные выше ответы являются хорошей информацией, но они не связаны напрямую с таймерами .net и поэтому вводят в заблуждение людей: (* ​​1015 *

Краткий ответ на оба вопроса автора по проекту . Почему они решили пойти по этому пути? Опасаетесь производительности всей системы? Кто знает ...
Чтобы не дублировать, см. Больше информации по обоим вопросам (и способам реализации точного таймеры на .net) в теме Яна .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...