.net - threading - timer tick
¿Por qué los temporizadores.NET están limitados a una resolución de 15 ms? (2)
Tenga en cuenta que estoy preguntando sobre algo que llamará a una función de devolución de llamada más a menudo que una vez cada 15 ms usando algo como System.Threading.Timer
. No estoy preguntando cómo QueryPerformanceCounter
con precisión una parte del código usando algo como System.Diagnostics.Stopwatch
o incluso QueryPerformanceCounter
.
Además, he leído las preguntas relacionadas:
Temporizador de Windows preciso? System.Timers.Timer () está limitado a 15 mseg
Temporizador de alta resolución en .NET
Ninguno de los cuales proporciona una respuesta útil a mi pregunta.
Además, el artículo recomendado de MSDN, Implementar una actualización continua, Proveedor de tiempo de alta resolución para Windows , trata de cronometrar cosas en lugar de proporcionar un flujo continuo de tics.
Con eso dicho. . .
Hay mucha información incorrecta sobre los objetos del temporizador .NET. Por ejemplo, System.Timers.Timer
se factura como "un temporizador de alto rendimiento optimizado para aplicaciones de servidor". Y System.Threading.Timer
se considera de alguna manera como un ciudadano de segunda clase. La sabiduría convencional es que System.Threading.Timer
es un contenedor de temporizadores de cola de temporizador de Windows y ese 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 echar un vistazo dentro de System.Timers.Timer
y verá la referencia a System.Threading.Timer
), y tiene algún código que proporcionará sincronización automática de hilos para que no tenga que hacerlo.
System.Threading.Timer
, como resulta, no es un envoltorio para los temporizadores de cola de 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 de .NET 4.0 también implementa su propia cola de 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:
Primero, ¿qué hace que la implementación del tiempo de ejecución de la cola del temporizador sea tan lenta? 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.
Segundo, y me doy cuenta de que esto es más difícil de responder, ¿por qué no utilizar los Temporizadores de cola de temporizador? 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?
Mi programa de prueba 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();
}
}
}
La resolución del temporizador viene dada por el latido del sistema. Esto normalmente tiene un valor predeterminado de 64 latidos / s que es 15.625 ms. Sin embargo, hay formas de modificar estas configuraciones de todo el sistema para lograr resoluciones de temporizador de hasta 1 ms o incluso hasta 0.5 ms en las plataformas más nuevas:
1. Ir por una resolución de 1 ms mediante la interfaz del temporizador multimedia:
La interfaz del temporizador multimedia es capaz de proporcionar una resolución de hasta 1 ms. Consulte Acerca de los temporizadores multimedia (MSDN), Obtención y configuración de la Resolución del temporizador (MSDN), y this respuesta para obtener más detalles sobre timeBeginPeriod
. Nota: no olvide llamar al timeEndPeriod para volver a la resolución predeterminada del temporizador cuando timeEndPeriod .
Cómo hacer:
#define TARGET_RESOLUTION 1 // 1-millisecond target resolution
TIMECAPS tc;
UINT wTimerRes;
if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR)
{
// Error; application can''t continue.
}
wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
timeBeginPeriod(wTimerRes);
// do your stuff here at approx. 1 ms timer resolution
timeEndPeriod(wTimerRes);
Nota: Este procedimiento también está disponible para otros procesos y la resolución obtenida se aplica a todo el sistema. La resolución más alta solicitada por cualquier proceso estará activa, tenga en cuenta las consecuencias.
2. Ir a una resolución de 0.5 ms:
Puede obtener una resolución de 0.5 ms mediante la API oculta NtSetTimerResolution()
. NtSetTimerResolution es exportado por la biblioteca nativa de Windows NT NTDLL.DLL. Consulte Cómo configurar la resolución del temporizador a 0.5ms? en MSDN. Sin embargo, la resolución verdadera alcanzable está determinada por el hardware subyacente. El hardware moderno es compatible con una resolución de 0,5 ms. Se pueden encontrar más detalles en Temporizadores de alta resolución dentro de Windows NT . Las resoluciones soportadas se pueden obtener llamando a NtQueryTimerResolution ().
Cómo hacer:
#define STATUS_SUCCESS 0
#define STATUS_TIMER_RESOLUTION_NOT_SET 0xC0000245
// after loading NtSetTimerResolution from ntdll.dll:
// The requested resolution in 100 ns units:
ULONG DesiredResolution = 5000;
// Note: The supported resolutions can be obtained by a call to NtQueryTimerResolution()
ULONG CurrentResolution = 0;
// 1. Requesting a higher resolution
// Note: This call is similar to timeBeginPeriod.
// However, it to to specify the resolution in 100 ns units.
if (NtSetTimerResolution(DesiredResolution ,TRUE,&CurrentResolution) != STATUS_SUCCESS) {
// The call has failed
}
printf("CurrentResolution [100 ns units]: %d/n",CurrentResolution);
// this will show 5000 on more modern platforms (0.5ms!)
// do your stuff here at 0.5 ms timer resolution
// 2. Releasing the requested resolution
// Note: This call is similar to timeEndPeriod
switch (NtSetTimerResolution(DesiredResolution ,FALSE,&CurrentResolution) {
case STATUS_SUCCESS:
printf("The current resolution has returned to %d [100 ns units]/n",CurrentResolution);
break;
case STATUS_TIMER_RESOLUTION_NOT_SET:
printf("The requested resolution was not set/n");
// the resolution can only return to a previous value by means of FALSE
// when the current resolution was set by this application
break;
default:
// The call has failed
}
Nota: La funcionalidad de NtSetTImerResolution está asignada básicamente a las funciones timeBeginPeriod
y timeEndPeriod
utilizando el valor bool Set
(ver Inside Windows NT High Resolution Timers para obtener más detalles sobre el esquema y todas sus implicaciones). Sin embargo, el conjunto multimedia limita la granularidad a milisegundos y NtSetTimerResolution permite establecer valores de milisegundos.
Tal vez el documento vinculado aquí lo explique un poco. Está algo seco, así que solo lo hojeé rápidamente :)
Citando la introducción:
La resolución del temporizador del sistema determina con qué frecuencia Windows realiza dos acciones principales:
- Actualice el conteo de tictac del temporizador si ha transcurrido un tic completo.
- Verifique si un objeto programado del temporizador ha expirado.
Un tic ticker es una noción del tiempo transcurrido que usa Windows para rastrear la hora del día e indicar tiempos cuánticos. Por defecto, la interrupción del reloj y el tic del temporizador son los mismos, pero Windows o una aplicación pueden cambiar el período de interrupción del reloj.
La resolución predeterminada del temporizador en Windows 7 es de 15,6 milisegundos (ms). Algunas aplicaciones reducen esto a 1 ms, lo que reduce el tiempo de funcionamiento de la batería en sistemas móviles hasta en un 25 por ciento.
Originalmente de: temporizadores, resolución de temporizador y desarrollo de código eficiente (docx).