¿Con qué frecuencia se actualiza DateTime.Now? o hay una API más precisa para obtener la hora actual?

Tengo un código ejecutándose en un bucle y está guardando el estado en función de la hora actual. A veces esto puede estar a solo milisegundos de distancia, pero por alguna razón parece que DateTime.Now siempre devolverá valores de al menos 10 ms de separación, incluso si solo es de 2 o 3 ms más tarde. Esto presenta un problema importante ya que el estado que estoy guardando depende del tiempo que se guardó (por ejemplo, grabar algo)

Mi código de prueba que devuelve cada valor 10 ms aparte:

public static void Main() { var dt1 = DateTime.Now; System.Threading.Thread.Sleep(2); var dt2 = DateTime.Now; // On my machine the values will be at least 10 ms apart Console.WriteLine("First: {0}, Second: {1}", dt1.Millisecond, dt2.Millisecond); } 

¿Hay alguna otra solución sobre cómo obtener la hora exacta hasta el milisegundo?

Alguien sugirió mirar la clase Cronómetro. Aunque la clase Cronómetro es muy precisa, no me dice la hora actual, algo que necesito para guardar el estado de mi progtwig.

Curiosamente, tu código funciona perfectamente bien en mi núcleo cuádruple bajo Win7, generando valores exactamente separados cada 2 ms casi siempre.

Así que hice una prueba más exhaustiva. Aquí está mi salida de ejemplo para Thread.Sleep(1) . El código imprime el número de ms entre llamadas consecutivas a DateTime.UtcNow en un bucle:

dormir 1

Cada fila contiene 100 caracteres, y por lo tanto representa 100 ms de tiempo en una “ejecución limpia”. Entonces esta pantalla cubre aproximadamente 2 segundos. La prioridad más larga fue 4ms; además, hubo un período que duró alrededor de 1 segundo cuando cada iteración tomó exactamente 1 ms. ¡Esa es casi la calidad del sistema operativo en tiempo real! 1 🙂

Así que lo intenté de nuevo, con Thread.Sleep(2) esta vez:

dormir 2

Nuevamente, resultados casi perfectos. Esta vez, cada fila tiene 200ms de longitud, y hay una carrera de casi 3 segundos de largo donde la brecha nunca fue diferente a exactamente 2ms.

Naturalmente, lo siguiente que ver es la resolución actual de DateTime.UtcNow en mi máquina. Aquí hay una carrera sin dormir; a . se imprime si UtcNow no cambió en absoluto :

sin dormir

Finalmente, mientras investigaba un extraño caso de marcas de tiempo separadas 15ms en la misma máquina que produjo los resultados anteriores, me he encontrado con las siguientes ocurrencias curiosas:

enter image description hereenter image description here

Hay una función en la API de Windows llamada timeBeginPeriod , que las aplicaciones pueden usar para boost temporalmente la frecuencia del temporizador, así que esto es probablemente lo que sucedió aquí. La documentación detallada de la resolución del temporizador está disponible a través del Hardware Dev Center Archive , específicamente Timer-Resolution.docx (un archivo de Word).

Conclusiones

  • DateTime.UtcNow puede tener una resolución mucho mayor que 15ms
  • Thread.Sleep(1) puede dormir exactamente 1ms
  • En mi máquina, UtcNow crece y crece exactamente 1ms a la vez (da o recibe un error de redondeo: el reflector muestra que hay una división en UtcNow ).
  • Es posible que el proceso cambie a un modo de baja resolución, cuando todo está basado en 15.6ms, y un modo de alta resolución, con cortes de 1ms, sobre la marcha.

Aquí está el código:

 static void Main(string[] args) { Console.BufferWidth = Console.WindowWidth = 100; Console.WindowHeight = 20; long lastticks = 0; while (true) { long diff = DateTime.UtcNow.Ticks - lastticks; if (diff == 0) Console.Write("."); else switch (diff) { case 10000: case 10001: case 10002: Console.ForegroundColor=ConsoleColor.Red; Console.Write("1"); break; case 20000: case 20001: case 20002: Console.ForegroundColor=ConsoleColor.Green; Console.Write("2"); break; case 30000: case 30001: case 30002: Console.ForegroundColor=ConsoleColor.Yellow; Console.Write("3"); break; default: Console.Write("[{0:0.###}]", diff / 10000.0); break; } Console.ForegroundColor = ConsoleColor.Gray; lastticks += diff; } } 

Resulta que existe una función no documentada que puede alterar la resolución del temporizador. No he investigado los detalles, pero pensé en publicar un enlace aquí: NtSetTimerResolution .

1 Por supuesto, me di cuenta de que el sistema operativo estaba lo más inactivo posible, y hay cuatro núcleos de CPU bastante potentes a su disposición. Si cargué los cuatro núcleos al 100%, la imagen cambia por completo, con preeventos largos en todas partes.

El problema con DateTime cuando se trata de milisegundos no se debe a la clase DateTime en absoluto, sino que tiene que ver con los ticks de la CPU y las divisiones de subprocesos. Básicamente, cuando el planificador detiene una operación para permitir que se ejecuten otros subprocesos, debe esperar al menos 1 segmento de tiempo antes de reanudar, que es alrededor de 15 ms en los SO modernos de Windows. Por lo tanto, cualquier bash de pausa por menos de esta precisión de 15 ms dará lugar a resultados inesperados.

Si toma una instantánea de la hora actual antes de hacer cualquier cosa, puede simplemente agregar el cronómetro al tiempo que almacenó, ¿no?

Debería preguntarse si realmente necesita un tiempo preciso, o simplemente un tiempo suficientemente cercano más un número creciente.

Puede hacer cosas buenas al obtener ahora () justo después de un evento de espera como mutex, select, poll, WaitFor *, etc., y luego agregar un número de serie a eso, quizás en el rango de nanosegundos o donde haya espacio.

También puede usar la instrucción máquina rdtsc (algunas bibliotecas proporcionan un envoltorio API para esto, no está seguro de hacer esto en C # o Java) para obtener tiempo barato de la CPU y combinarlo con el tiempo a partir de ahora (). El problema con rdtsc es que en sistemas con escalado de velocidad nunca se puede estar seguro de lo que va a hacer. También se envuelve con bastante rapidez.

Todo lo que utilicé para realizar esta tarea con precisión del 100% es un control de temporizador y una etiqueta.

El código no requiere mucha explicación, bastante simple. Variables globales:

 int timer = 0; 

Este es el evento tic:

 private void timeOfDay_Tick(object sender, EventArgs e) { timeOfDay.Enabled = false; timer++; if (timer <= 1) { timeOfDay.Interval = 1000; timeOfDay.Enabled = true; lblTime.Text = "Time: " + DateTime.Now.ToString("h:mm:ss tt"); timer = 0; } } 

Aquí está la carga del formulario:

 private void DriverAssignment_Load(object sender, EventArgs e) { timeOfDay.Interval= 1; timeOfDay.Enabled = true; } 

Respondiendo a la segunda parte de su pregunta con respecto a una API más precisa, el comentario de AnotherUser me llevó a esta solución que en mi escenario supera el problema de precisión DateTime.Now:

 static FileTime time; public static DateTime Now() { GetSystemTimePreciseAsFileTime(out time); var newTime = (ulong)time.dwHighDateTime << (8 * 4) | time.dwLowDateTime; var newTimeSigned = Convert.ToInt64(newTime); return new DateTime(newTimeSigned).AddYears(1600).ToLocalTime(); } public struct FileTime { public uint dwLowDateTime; public uint dwHighDateTime; } [DllImport("Kernel32.dll")] public static extern void GetSystemTimePreciseAsFileTime(out FileTime lpSystemTimeAsFileTime); 

En mis propios puntos de referencia, iterando 1M, regresa en un promedio de 3 ticks vs DateTime.Now 2 tics.

Por qué 1600 está fuera de mi jurisdicción, pero lo uso para obtener el año correcto.

EDITAR: Este sigue siendo un problema en win10. Cualquiera interesado puede ejecutar esta paz de evidencia:

 void Main() { for (int i = 0; i < 100; i++) { Console.WriteLine(Now().ToString("yyyy-MM-dd HH:mm:ss.fffffff")); Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff")); Console.WriteLine(); } } // include the code above 

Puede usar DateTime.Now.Ticks, lea artical en MSDN

“Una sola marca representa cien nanosegundos o una décima millonésima de segundo”.