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