Подтвердить что ты не робот

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

Обратите внимание, что я спрашиваю о том, что вызовет функцию обратного вызова чаще, чем раз в 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 Timer Queue Timers и что 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 общего источника показывают, что среда выполнения выполняет свою собственную очередь таймера, которая похожа на таймеры очереди таймера, но никогда не называет функции 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 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();
        }
    }
}
4b9b3361

Ответ 1

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

Цитирование ввода:

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

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

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

Ответ 4

Это надежный до 1 мкс в качестве таймера в зависимости от времени переключения контекста, среди прочего, таких как, но не ограничиваясь, счетчики в сетевом стеке.

См. http://www.codeproject.com/Articles/571289/Obtaining-Microsecond-Precision-in-NET

Он обеспечивает его гораздо меньше, чем все, что вы видели раньше, а также превосходит счетчики производительности!

В вашей операционной системе или сетевом стеке или базовых драйверах программного обеспечения PROBABLY есть другие подобные оговорки, которые влияют на точность этого механизма; однако, поскольку эту точность можно разделить на приемлемые и неприемлемые допуски в зависимости от таких факторов, как, помимо прочего, температура, можно использовать эти доли в соответствии с окружающей средой и вышеупомянутыми пунктами для определения наилучшей реакции в такое время.