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