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