Tenga en cuenta que estoy preguntando sobre algo que llamará a una función de devolución de llamada con más frecuencia que una vez cada 15 ms utilizando algo como System.Threading.Timer
. No estoy preguntando cómo sincronizar con precisión un código usando algo como System.Diagnostics.Stopwatch
o incluso QueryPerformanceCounter
.¿Por qué los temporizadores .NET están limitados a una resolución de 15 ms?
Además, he leído las preguntas relacionadas:
Accurate Windows timer? System.Timers.Timer() is limited to 15 msec
Ninguno de los cuales suministra una respuesta útil a mi pregunta.
Además, el artículo de MSDN recomendado, Implement a Continuously Updating, High-Resolution Time Provider for Windows, se trata de temporización cosas en lugar de proporcionar un flujo continuo de garrapatas.
Con eso dicho. . .
Hay un montón de mala información que hay acerca de los objetos de temporizador .NET. Por ejemplo, System.Timers.Timer
se factura como "un temporizador de alto rendimiento optimizado para aplicaciones de servidor". Y System.Threading.Timer
de alguna manera se considera un ciudadano de segunda clase. La sabiduría convencional es que System.Threading.Timer
es un contenedor alrededor de Windows Timer Queue Timers y que System.Timers.Timer
es algo completamente diferente.
La realidad es muy diferente. System.Timers.Timer
es solo un contenedor de componentes delgados alrededor de System.Threading.Timer
(solo use Reflector o ILDASM para mirar dentro de System.Timers.Timer
y verá la referencia al System.Threading.Timer
), y tiene algún código que proporcionará la sincronización automática del hilo para que no tenga que hacerlo.
System.Threading.Timer
, como resulta no es una envoltura para los Temporizadores de cola del temporizador. Al menos no en el tiempo de ejecución 2.0, que se usó de .NET 2.0 a .NET 3.5. Unos minutos con la CLI de fuente compartida muestra que el tiempo de ejecución implementa su propia cola del temporizador que es similar a los Temporizadores de cola del temporizador, pero nunca llama a las funciones de Win32.
Parece que el tiempo de ejecución .NET 4.0 también implementa su propia cola del temporizador. Mi programa de prueba (ver a continuación) proporciona resultados similares en .NET 4.0 como lo hace en .NET 3.5. Creé mi propia envoltura administrada para los Temporizadores de cola de temporizador y probé que puedo obtener una resolución de 1 ms (con una precisión bastante buena), por lo que considero poco probable que esté leyendo la fuente CLI incorrectamente.
Tengo dos preguntas:
En primer lugar, lo que provoca la ejecución del tiempo de ejecución de la cola del temporizador a ser tan lento? No puedo obtener una resolución superior a 15 ms, y la precisión parece estar en el rango de -1 a +30 ms. Es decir, si pido 24 ms, obtengo tics en cualquier lugar de 23 a 54 ms. Supongo que podría pasar más tiempo con la fuente CLI para rastrear la respuesta, pero pensé que alguien aquí podría saber.
En segundo lugar, y me di cuenta de que esto es más difícil de responder, ¿por qué no usar el temporizador Temporizador de cola? Me doy cuenta de que .NET 1.x tuvo que ejecutarse en Win9x, que no tenía esas API, pero que existían desde Windows 2000, que si mal no recuerdo fue el requisito mínimo para .NET 2.0. ¿Es porque la CLI tuvo que ejecutarse en cajas que no son de Windows?
Mis temporizadores programa de pruebas:
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();
}
}
}
¡Buena pregunta! Me interesa si alguien realmente tiene una idea de esto. ¿Alguien del equipo clr en SO? –
Apostaría que win9x también tenía marcas en el kernel. después de todo, era un entorno de tareas preventivas. toda una multitarea de mierda pero real. por lo que no puede hacer prioridad sin interrupciones, y tienden a disparar desde un temporizador de hardware normal, especialmente en los años 90, basado en el HTC (64 Hz). ¿Cómo se hace GUI sin una cola de eventos? el kernel lleva los eventos del temporizador a la cola de eventos, no hay forma de evitarlos porque, mientras su aplicación no hace nada esperando la cola, se desactiva. –