c# - tick - El temporizador más preciso en.NET?
timer event c# (8)
Aquí hay otro enfoque. Preciso dentro de 5-20ms en mi máquina.
public class Run
{
public Timer timer;
public Run()
{
var nextSecond = MilliUntilNextSecond();
var timerTracker = new TimerTracker()
{
StartDate = DateTime.Now.AddMilliseconds(nextSecond),
Interval = 1000,
Number = 0
};
timer = new Timer(TimerCallback, timerTracker, nextSecond, -1);
}
public class TimerTracker
{
public DateTime StartDate;
public int Interval;
public int Number;
}
void TimerCallback(object state)
{
var timeTracker = (TimerTracker)state;
timeTracker.Number += 1;
var targetDate = timeTracker.StartDate.AddMilliseconds(timeTracker.Number * timeTracker.Interval);
var milliDouble = Math.Max((targetDate - DateTime.Now).TotalMilliseconds, 0);
var milliInt = Convert.ToInt32(milliDouble);
timer.Change(milliInt, -1);
Console.WriteLine(DateTime.Now.ToString("ss.fff"));
}
public static int MilliUntilNextSecond()
{
var time = DateTime.Now.TimeOfDay;
var shortTime = new TimeSpan(0, time.Hours, time.Minutes, time.Seconds, 0);
var oneSec = new TimeSpan(0, 0, 1);
var milliDouble = (shortTime.Add(oneSec) - time).TotalMilliseconds;
var milliInt = Convert.ToInt32(milliDouble);
return milliInt;
}
}
Ejecutar el siguiente código (ligeramente pseudo) produce los siguientes resultados. Estoy sorprendido de lo inexacto que es el cronómetro (gana ~ 14ms cada marca).
¿Hay algo más preciso por ahí?
void Main()
{
var timer = new System.Threading.Timer(TimerCallback, null, 0, 1000);
}
void TimerCallback(object state)
{
Debug.WriteLine(DateTime.Now.ToString("ss.ffff"));
}
Sample Output:
...
11.9109
12.9190
13.9331
14.9491
15.9632
16.9752
17.9893
19.0043
20.0164
21.0305
22.0445
23.0586
24.0726
25.0867
26.1008
27.1148
28.1289
29.1429
30.1570
31.1710
32.1851
Creo que las otras respuestas no abordan por qué hay 14 ms en cada iteración del código del OP; no es debido a un reloj del sistema impreciso (y DateTime.Now
no es impreciso, a menos que haya desactivado los servicios NTP o configurado un huso horario incorrecto o ¡algo tonto! Es solo impreciso ).
Temporizador preciso
Incluso con un reloj del sistema impreciso (haciendo uso de DateTime.Now
, o tener una célula solar conectada a un ADC para decir qué tan alto está el sol en el cielo, o dividir el tiempo entre las mareas altas, o ...), el código seguir este patrón tendrá un promedio de cero (será perfectamente exacto con exactamente un segundo entre los tics en promedio):
var interval = new TimeSpan(0, 0, 1);
var nextTick = DateTime.Now + interval;
while (true)
{
while ( DateTime.Now < nextTick )
{
Thread.Sleep( nextTick - DateTime.Now );
}
nextTick += interval; // Notice we''re adding onto when the last tick was supposed to be, not when it is now
// Insert tick() code here
}
(Si está copiando y pegando esto, tenga cuidado con los casos en los que su código tarda más de un interval
en ejecutarse. Lo dejaré como un ejercicio para que el lector encuentre las formas fáciles de hacer que salte tantos golpes como se necesita para nextTick
aterrizar en el futuro)
Temporizador inexacto
Supongo que la implementación de System.Threading.Timer de Microsoft sigue este tipo de patrón. Este patrón siempre habrá desaparecido incluso con un temporizador de sistema perfectamente preciso y perfectamente preciso (porque lleva tiempo ejecutar incluso la operación de adición):
var interval = new TimeSpan(0, 0, 1);
var nextTick = DateTime.Now + interval;
while (true)
{
while ( DateTime.Now < nextTick )
{
Thread.Sleep( nextTick - DateTime.Now );
}
nextTick = DateTime.Now + interval; // Notice we''re adding onto .Now instead of when the last tick was supposed to be. This is where slew comes from
// Insert tick() code here
}
Entonces, para las personas que podrían estar interesadas en rodar su propio temporizador, no sigan este segundo patrón.
Medición precisa del tiempo
Como han dicho otros carteles, la clase Stopwatch
proporciona una gran precisión para la medición del tiempo, pero no ayuda en absoluto con exactitud si se sigue un patrón incorrecto. Pero, como @Shahar dijo que no es como si fuera a tener un temporizador perfectamente preciso para empezar, entonces debes reconsiderar las cosas si lo que buscas es una precisión perfecta.
Descargo de responsabilidad
Tenga en cuenta que Microsoft no habla mucho sobre los System.Threading.Timer internos de la clase System.Threading.Timer , así que estoy educadamente especulando al respecto, pero si grazna como un pato, entonces es probable que sea un pato. Además, me doy cuenta de que esto tiene varios años, pero sigue siendo una pregunta relevante (y creo que no contestada).
Editar: cambió el enlace a la respuesta de @ Shahar
Editar: Microsoft tiene un código fuente para muchas cosas en línea, incluyendo System.Threading.Timer , para cualquiera que esté interesado en ver cómo Microsoft implementó ese temporizador de sincronización.
El sistema operativo de escritorio (como Windows) no es un sistema operativo en tiempo real. lo que significa que no puede esperar una precisión total y no puede obligar al programador a activar su código en el milisegundo exacto que desee. Especialmente en la aplicación .NET que no es determinista ... por ejemplo, cada vez que el GC puede comenzar a recopilar, una compilación JIT puede ser un poco más lenta o un poco más rápida ....
Hice una clase para eso, y parece estar funcionando bien. Sin inexactitud alguna:
class AccurateTimer
{
public event EventHandler<EventArgs> Tick;
public bool Running { get; private set; }
public int Interval { get; private set; }
public AccurateTimer(int interval_ = 1000)
{
Running = false;
Interval = interval_;
}
public void Start()
{
Running = true;
Thread thread = new Thread(Run);
thread.Start();
}
public void Stop()
{
Running = false;
}
private void Run()
{
DateTime nextTick = DateTime.Now.AddMilliseconds(Interval);
while (Running)
{
if (DateTime.Now > nextTick)
{
nextTick = nextTick.AddMilliseconds(Interval);
OnTick(EventArgs.Empty);
}
}
}
protected void OnTick(EventArgs e)
{
EventHandler<EventArgs> copy = Tick;
if (copy != null)
{
copy(this, e);
}
}
}
Sin embargo, puede que no sea la mejor solución.
No es el temporizador que es inexacto, pero http://blogs.msdn.com/b/ericlippert/archive/2010/04/08/precision-and-accuracy-of-datetime.aspx , que tiene una tolerancia anunciada de 16 ms.
En cambio, usaría la propiedad Environment.Ticks para medir los ciclos de la CPU durante esta prueba.
Editar : Environment.Ticks también se basa en el temporizador del sistema y puede tener los mismos problemas de precisión que DateTime.Now. Aconsejaría elegir el StopWatch
lugar, como han mencionado muchos otros contestadores.
Para medir el tiempo exacto, debe utilizar la clase Cronómetro MSDN
También he escrito una clase que tiene una precisión de 1 ms. Tomé el código de Hans Passant del foro
https://social.msdn.microsoft.com/Forums/en-US/6cd5d9e3-e01a-49c4-9976-6c6a2f16ad57/1-millisecond-timer
y lo envolvió en una clase para facilitar su uso en su Formulario. Puede configurar múltiples temporizadores fácilmente si lo desea. En el código de ejemplo a continuación, he usado 2 temporizadores. Lo he probado y funciona bien.
// AccurateTimer.cs
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace YourProjectsNamespace
{
class AccurateTimer
{
private delegate void TimerEventDel(int id, int msg, IntPtr user, int dw1, int dw2);
private const int TIME_PERIODIC = 1;
private const int EVENT_TYPE = TIME_PERIODIC;// + 0x100; // TIME_KILL_SYNCHRONOUS causes a hang ?!
[DllImport("winmm.dll")]
private static extern int timeBeginPeriod(int msec);
[DllImport("winmm.dll")]
private static extern int timeEndPeriod(int msec);
[DllImport("winmm.dll")]
private static extern int timeSetEvent(int delay, int resolution, TimerEventDel handler, IntPtr user, int eventType);
[DllImport("winmm.dll")]
private static extern int timeKillEvent(int id);
Action mAction;
Form mForm;
private int mTimerId;
private TimerEventDel mHandler; // NOTE: declare at class scope so garbage collector doesn''t release it!!!
public AccurateTimer(Form form,Action action,int delay)
{
mAction = action;
mForm = form;
timeBeginPeriod(1);
mHandler = new TimerEventDel(TimerCallback);
mTimerId = timeSetEvent(delay, 0, mHandler, IntPtr.Zero, EVENT_TYPE);
}
public void Stop()
{
int err = timeKillEvent(mTimerId);
timeEndPeriod(1);
System.Threading.Thread.Sleep(100);// Ensure callbacks are drained
}
private void TimerCallback(int id, int msg, IntPtr user, int dw1, int dw2)
{
if (mTimerId != 0)
mForm.BeginInvoke(mAction);
}
}
}
// FormMain.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace YourProjectsNamespace
{
public partial class FormMain : Form
{
AccurateTimer mTimer1,mTimer2;
public FormMain()
{
InitializeComponent();
}
private void FormMain_Load(object sender, EventArgs e)
{
int delay = 10; // In milliseconds. 10 = 1/100th second.
mTimer1 = new AccurateTimer(this, new Action(TimerTick1),delay);
delay = 100; // 100 = 1/10th second.
mTimer2 = new AccurateTimer(this, new Action(TimerTick2), delay);
}
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
mTimer1.Stop();
mTimer2.Stop();
}
private void TimerTick1()
{
// Put your first timer code here!
}
private void TimerTick2()
{
// Put your second timer code here!
}
}
}
Timer y DateTime no tienen la precisión suficiente para su propósito. Pruebe el MSDN lugar. Mira el siguiente artículo para más detalles:
http://blogs.msdn.com/b/ericlippert/archive/2010/04/08/precision-and-accuracy-of-datetime.aspx