C#¿Por qué las frecuencias de temporización son extremadamente bajas?
timer frequency (10)
Ambos System.Timers.Timer
y System.Threading.Timer
disparan a intervalos que son considerablemente diferentes de los solicitados. Por ejemplo:
new System.Timers.Timer(1000d / 20);
produce un temporizador que dispara 16 veces por segundo, no 20.
Para estar seguro de que no hay efectos secundarios de manejadores de eventos demasiado largos, escribí este pequeño programa de prueba:
int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };
// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
int count = 0;
// Initialize timer
System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
timer.Elapsed += delegate { Interlocked.Increment(ref count); };
// Count for 10 seconds
DateTime start = DateTime.Now;
timer.Enabled = true;
while (DateTime.Now < start + TimeSpan.FromSeconds(10))
Thread.Sleep(10);
timer.Enabled = false;
// Calculate actual frequency
Console.WriteLine(
"Requested frequency: {0}/nActual frequency: {1}/n",
frequency, count / 10d);
}
La salida se ve así:
Solicitado: 5 Hz; real: 4,8 Hz
Solicitado: 10 Hz; real: 9,1 Hz
Solicitado: 15 Hz; real: 12,7 Hz
Solicitado: 20 Hz; real: 16 Hz
Solicitado: 30 Hz; real: 21,3 Hz
Solicitado: 50 Hz; real: 31,8 Hz
Solicitado: 75 Hz; real: 63,9 Hz
Solicitado: 100 Hz; real: 63,8 Hz
Solicitado: 200 Hz; real: 63,9 Hz
Solicitado: 500 Hz; real: 63,9 Hz
La frecuencia real se desvía hasta en un 36% con respecto a la solicitada. (Y evidentemente no puede exceder los 64 Hz.) Dado que Microsoft recomienda este temporizador para su "mayor precisión" sobre System.Windows.Forms.Timer
, esto me desconcierta.
Por cierto, estas no son desviaciones al azar. Son los mismos valores todo el tiempo. Y un programa de prueba similar para la otra clase de temporizador, System.Threading.Timer
, muestra exactamente los mismos resultados.
En mi programa real, necesito recolectar mediciones exactamente a 50 muestras por segundo. Esto aún no debería requerir un sistema en tiempo real. Y es muy frustrante obtener 32 muestras por segundo en lugar de 50.
¿Algunas ideas?
@Chris: Tienes razón, los intervalos parecen ser múltiplos enteros de algo alrededor de 1/64 de segundo. Por cierto, agregando un Thread.Sleep (...) en el evento controlador no hace ninguna diferencia. Esto tiene sentido dado que System.Threading.Timer
utiliza el grupo de subprocesos, por lo que cada evento se desencadena en un hilo libre.
Bueno, estoy obteniendo un número diferente de hasta 100 Hz en realidad, con algunas desviaciones grandes, pero en la mayoría de los casos más cerca del número solicitado (ejecutando XP SP3 con los SP .NET más recientes).
El System.Timer.Timer se implementa utilizando System.Threading.Timer, por lo que esto explica por qué ve los mismos resultados. Supongo que el temporizador se implementa utilizando algún tipo de algoritmo de programación, etc. (es una llamada interna, tal vez mirar a Rotor 2.0 podría arrojar algo de luz sobre él).
Sugeriría implementar un tipo de temporizador usando otro hilo (o una combinación de ambos) llamando a Sleep y a callback. Sin embargo, no estoy seguro del resultado.
De lo contrario, podría echar un vistazo a los temporizadores multimedia (PInvoke).
Estas clases no están pensadas para uso en tiempo real y están sujetas a la naturaleza de programación dinámica de un sistema operativo como Windows. Si necesita una ejecución en tiempo real, probablemente quiera mirar algún hardware incrustado. No estoy 100% seguro, pero creo que .netcpu puede ser una versión en tiempo real de un runtime .NET más pequeño en un chip.
http://www.arm.com/markets/emerging_applications/armpp/8070.html
Por supuesto, debe evaluar qué tan importante es la precisión de esos intervalos para que el código adjunto se ejecute en un sistema operativo que no sea en tiempo real. A menos, por supuesto, que esta sea una pregunta puramente académica (en cuyo caso, sí, ¡es interesante !: P).
Hay muchas razones por las que las frecuencias del temporizador están apagadas. Ejemplo con el hardware, el mismo hilo está ocupado con otro procesamiento, y así sucesivamente ...
Si desea más tiempo con precisión, use la clase Cronómetro en el espacio de nombres System.Diagnostics.
Parece que tus frecuencias de temporizador reales son 63.9 Hz o múltiplos enteros de las mismas.
Eso implicaría una resolución de temporizador de aproximadamente 15 mseg (o múltiplos enteros de la misma, es decir, 30 mseg, 45 mseg, etc.).
Esto, temporizadores basados en múltiplos enteros de un ''tic'', es de esperar (en DOS, por ejemplo, el valor ''tic'' fue de 55 mseg / 18 Hz).
No sé por qué tu recuento de garrapatas es de 15.65 mec en lugar de 15 mseg. Como experimento, ¿qué sucede si duermes durante varios milisegundos dentro del controlador del temporizador? ¿Podríamos estar viendo 15 mseg entre los tics y 0.65 mseg en tu controlador de temporizador en cada tic?
Parte del problema es que los temporizadores tienen dos retrasos en la cuenta. Esto puede variar dependiendo de cómo se implementa el temporizador en el sistema operativo.
- El tiempo que solicita esperar
- El tiempo entre el momento en que ocurre el # 1 y el proceso llega a su turno en la cola de programación.
El temporizador tiene un buen control de # 1 pero casi no tiene control sobre # 2. Puede indicarle al sistema operativo que le gustaría volver a ejecutar, pero el SO es libre de activarlo cuando lo desee.
Si necesita dar el salto a un entorno en tiempo real, he utilizado RTX en el pasado cuando se necesitaba un muestreo determinista (desde un dispositivo serial personalizado) y tuve muy buena suerte con él.
Windows (y, por lo tanto, .NET se ejecuta en la parte superior) es un sistema operativo multitarea preventiva. Cualquier subproceso dado puede detenerse en cualquier momento por otro subproceso, y si el subproceso de apropiación no se comporta correctamente, no podrá recuperar el control cuando lo necesite o lo necesite .
Eso, en pocas palabras, es la razón por la cual no se puede garantizar que obtenga los tiempos exactos y por qué Windows y .NET son plataformas inadecuadas para ciertos tipos de software. Si las vidas están en peligro debido a que no obtiene el control EXACTAMENTE cuando lo desea, elija una plataforma diferente.
Según tus comentarios, no deberías estar usando un temporizador. Deberías estar usando un ciclo con un cronómetro para verificar el intervalo y un spinlock para no perder el quantum.
Si usa winmm.dll puede usar más tiempo de CPU, pero tener un mejor control.
Aquí está su ejemplo modificado para usar los temporizadores winmm.dll
const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";
delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);
[DllImport(WINMM)]
static extern uint timeSetEvent(
UInt32 uDelay,
UInt32 uResolution,
[MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,
UInt32 dwUser,
Int32 fuEvent
);
[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);
// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);
static long CPUFrequency;
static int count;
static void Main(string[] args)
{
QueryPerformanceFrequency(out CPUFrequency);
int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };
foreach (int freq in frequencies)
{
count = 0;
long start = GetTimestamp();
// start timer
uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);
// wait 10 seconds
while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
{
Thread.Sleep(1);
}
// end timer
timeKillEvent(timerId);
Console.WriteLine("Requested frequency: {0}/nActual frequency: {1}/n", freq, count / 10);
}
Console.ReadLine();
}
static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
Interlocked.Increment(ref count);
}
static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}
static public long GetTimestamp()
{
long result;
QueryPerformanceCounter(out result);
return result;
}
Y aquí está la salida que obtengo:
Requested frequency: 5
Actual frequency: 5
Requested frequency: 10
Actual frequency: 10
Requested frequency: 15
Actual frequency: 15
Requested frequency: 20
Actual frequency: 19
Requested frequency: 30
Actual frequency: 30
Requested frequency: 50
Actual frequency: 50
Requested frequency: 75
Actual frequency: 76
Requested frequency: 100
Actual frequency: 100
Requested frequency: 200
Actual frequency: 200
Requested frequency: 500
Actual frequency: 500
Espero que esto ayude.
Aquí hay una buena implementación de un temporizador usando el temporizador multimedia http://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html