2010-09-19 5 views
52

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

High resolution timer in .NET

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

¡Buena pregunta! Me interesa si alguien realmente tiene una idea de esto. ¿Alguien del equipo clr en SO? –

+0

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. –

Respuesta

27

Quizás el documento vinculado aquí lo explica un poco. Es un poco seco, así que sólo navegado it :) rápidamente

Citando la intro:

La resolución del temporizador del sistema determina la frecuencia con Windows realiza dos acciones principales:

  • Actualizar el paso del temporizador cuente si ha transcurrido un tiempo completo.
  • Compruebe si ha expirado un objeto de temporizador programado .

un paso del temporizador es una noción de tiempo transcurrido que Windows utiliza para controlar el tiempo del día y el hilo tiempos cuántica. De forma predeterminada, la interrupción del reloj y la marca del temporizador son iguales, 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 15.6 milisegundos (ms). Algunas aplicaciones reducen esto a 1 ms, lo que reduce el tiempo de ejecución de la batería en sistemas móviles por tanto como 25 por ciento.

Originalmente desde: Timers, Timer Resolution, and Development of Efficient Code (docx).

+0

Gracias por el enlace, Arnold. Eso no responde a mi pregunta por completo, pero da una idea, especialmente el poco acerca de la resolución predeterminada del temporizador de 15.6 ms. –

+0

Sí, no tuve la sensación de que lo explicara todo pero tuviera alguna información interesante. –

+0

Consulte también la respuesta en el hilo ["¿Cómo establecer la resolución del temporizador de C# a 1 ms?"] (Http://stackoverflow.com/questions/15071359). –

13

La resolución del temporizador está dada por el latido del corazón del sistema. Esto normalmente tiene un valor predeterminado de 64 latidos/s que es 15.625 ms. Sin embargo, hay maneras de modificar estos ajustes a nivel de sistema para lograr resoluciones del temporizador de hasta 1 ms o incluso a 0,5 ms en las plataformas más recientes:

1. A por 1 ms de resolución por medio de la interfaz de temporizador multimedia:

La interfaz del temporizador multimedia puede proporcionar una resolución de hasta 1 ms. Consulte About Multimedia Timers (MSDN), Obtaining and Setting Timer Resolution (MSDN) y this para obtener más información sobre timeBeginPeriod. Nota: No olvide llamar al timeEndPeriod para volver a la resolución predeterminada del temporizador cuando haya terminado.

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 es liviano a otros procesos, así y la resolución obtenida se aplica en todo el sistema. La resolución más alta solicitada por cualquier proceso estará activa, tenga en cuenta las consecuencias.

2. Ir a la resolución de 0,5 ms:

Usted puede obtener 0,5 ms resolución por medio de la API oculta NtSetTimerResolution(). NtSetTimerResolution es exportado por la biblioteca nativa de Windows NT NTDLL.DLL. Ver How to set timer resolution to 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. Aún más detalles se encuentran en Inside Windows NT High Resolution Timers. 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 es básicamente asigna a las funciones timeBeginPeriodytimeEndPeriod utilizando el valor bool Set (ver Inside Windows NT High Resolution Timers para 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.

+0

Según mis experimentos, creo que el código de error STATUS_TIMER_RESOLUTION_NOT_SET debería ser 0xC0000245, no 245 –

+0

@RolandPihlakas. Es correcto, gracias. He editado mi respuesta para corregir esto. – Arno

Cuestiones relacionadas