Por que os temporizadores .NET estão limitados a uma resolução de 15 ms?

Observe que estou perguntando sobre algo que chamará uma função de retorno de chamada mais frequentemente do que uma vez a cada 15 ms usando algo comoSystem.Threading.Timer. Eu não estou perguntando sobre como cronometrar com precisão um pedaço de código usando algo comoSystem.Diagnostics.Stopwatch ou mesmoQueryPerformanceCounter.

Além disso, li as perguntas relacionadas:

Temporizador preciso do Windows? System.Timers.Timer () está limitado a 15 ms

Temporizador de alta resolução no .NET

Nenhum dos dois fornece uma resposta útil para minha pergunta.

Além disso, o artigo recomendado do MSDN,Implementar um provedor de tempo de alta resolução e atualização contínua para Windows, trata-se de cronometrar as coisas em vez de fornecer um fluxo contínuo de ticks.

Com isso dito. . .

Existem muitas informações ruins sobre os objetos de timer do .NET. Por exemplo,System.Timers.Timer é cobrado como "um cronômetro de alto desempenho otimizado para aplicativos de servidor". ESystem.Threading.Timer é de alguma forma considerado um cidadão de segunda classe. A sabedoria convencional é queSystem.Threading.Timer é um invólucro no WindowsTemporizadores da fila de temporizadores e essaSystem.Timers.Timer é algo completamente diferente.

A realidade é muito diferente.System.Timers.Timer é apenas um invólucro de componente finoSystem.Threading.Timer (basta usar o Reflector ou o ILDASM para espiar dentroSystem.Timers.Timer e você verá a referência aSystem.Threading.Timer) e possui algum código que fornecerá sincronização automática de threads, para que você não precise fazer isso.

System.Threading.Timer, como se vênão é um invólucro para os Timers da fila do timer. Pelo menos não no tempo de execução 2.0, que foi usado do .NET 2.0 ao .NET 3.5. Alguns minutos com a CLI de origem compartilhada mostram que o tempo de execução implementa sua própria fila de timer semelhante aos Timers da fila de timer, mas na verdade nunca chama as funções do Win32.

Parece que o tempo de execução do .NET 4.0 também implementa sua própria fila de timer. Meu programa de teste (veja abaixo) fornece resultados semelhantes no .NET 4.0 e no .NET 3.5. Criei meu próprio wrapper gerenciado para os Timers de filas do timer e provei que posso obter uma resolução de 1 ms (com uma precisão bastante boa), por isso considero improvável que esteja lendo a fonte da CLI errado.

Eu tenho duas perguntas:

Primeiro, o que faz com que a implementação do tempo de execução da fila do timer seja tão lenta? Não consigo obter uma resolução superior a 15 ms e a precisão parece estar no intervalo de -1 a +30 ms. Ou seja, se eu pedir 24 ms, recebo carrapatos de 23 a 54 ms. Suponho que poderia passar mais tempo com a fonte CLI para rastrear a resposta, mas pensei que alguém aqui pudesse saber.

Segundo, e percebo que isso é mais difícil de responder, por que não usar os Timers da Fila do Timer? Percebo que o .NET 1.x precisava ser executado no Win9x, que não possuía essas APIs, mas elas existem desde o Windows 2000, o que, se bem me lembro, era o requisito mínimo para o .NET 2.0. É porque a CLI teve que ser executada em caixas não Windows?

Meu programa de teste de temporizadores:

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();
        }
    }
}

questionAnswers(3)

yourAnswerToTheQuestion