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