¿Cómo obtener la marca de tiempo de la precisión de tics en .NET / C #?

Hasta ahora usaba DateTime.Now para obtener marcas de tiempo, pero noté que si imprime DateTime.Now en un bucle, verá que aumenta en saltos decrecimiento de aprox. 15 ms. Pero para ciertos escenarios en mi aplicación necesito obtener la marca de tiempo más precisa posible, preferiblemente con precisión tic (= 100 ns). ¿Algunas ideas?

Actualizar:

Aparentemente, QueryPerformanceCounter / QueryPerformanceCounter es el camino a seguir, pero solo se puede usar para medir el tiempo, así que estaba pensando en llamar a DateTime.Now cuando la aplicación se inicia y luego ejecutar StopWatch y luego agregar el tiempo transcurrido desde StopWatch al valor inicial devuelto por DateTime.Now . Al menos eso debería darme marcas de tiempo relativas precisas, ¿verdad? ¿Qué piensas de eso (hack)?

NOTA:

StopWatch.ElapsedTicks es diferente de StopWatch.Elapsed.Ticks ! Usé el primero suponiendo 1 tick = 100 ns, pero en este caso 1 tick = 1 / StopWatch.Frequency . Entonces, para obtener marcas equivalentes a DateTime, use StopWatch.Elapsed.Ticks . Acabo de aprender esto de la manera difícil.

NOTA 2:

Usando el enfoque de cronómetro, noté que no se sincronizaba con el tiempo real. Después de aproximadamente 10 horas, estaba adelantado por 5 segundos. Así que supongo que uno tendría que volver a sincronizar cada X o algo así, donde X podría ser de 1 hora, 30 minutos, 15 minutos, etc. No estoy seguro de cuál sería el intervalo de tiempo óptimo para la resincronización, ya que cada resincronización cambiará el offset que puede ser hasta 20 ms.

El valor del reloj del sistema que DateTime.Now lee solo se actualiza cada 15 ms aproximadamente (o 10 ms en algunos sistemas), por lo que los tiempos se cuantifican en esos intervalos. Existe un efecto de cuantificación adicional que resulta del hecho de que su código se está ejecutando en un sistema operativo multiproceso y, por lo tanto, existen tramos donde su aplicación no está “activa” y, por lo tanto, no mide la hora actual.

Ya que está buscando un valor de marca de tiempo ultra preciso (en lugar de limitar el tiempo de una duración arbitraria), la clase Stopwatch por sí sola no hará lo que necesita. Creo que tendrías que hacer esto tú mismo con una especie de híbrido DateTime / Stopwatch . Cuando se inicia la aplicación, debe almacenar el valor actual de DateTime.UtcNow (es decir, el tiempo de resolución cruda cuando se inicia la aplicación) y luego también iniciar un objeto de Stopwatch , como este:

 DateTime _starttime = DateTime.UtcNow; Stopwatch _stopwatch = Stopwatch.StartNew(); 

Entonces, cada vez que necesite un valor DateTime alta resolución, lo obtendría así:

 DateTime highresDT = _starttime.AddTicks(_stopwatch.Elapsed.Ticks); 

También es posible que desee restablecer periódicamente _starttime y _stopwatch, para evitar que el tiempo resultante se desincronice demasiado con la hora del sistema (aunque no estoy seguro de que esto realmente suceda, y de todos modos tomaría mucho tiempo) .

Actualización : dado que parece que Cronómetro no se sincroniza con la hora del sistema (hasta medio segundo por hora), creo que tiene sentido restablecer la clase híbrida DateTime función de la cantidad de tiempo que transcurre entre las llamadas a mira la hora:

 public class HiResDateTime { private static DateTime _startTime; private static Stopwatch _stopWatch = null; private static TimeSpan _maxIdle = TimeSpan.FromSeconds(10); public static DateTime UtcNow { get { if ((_stopWatch == null) || (_startTime.Add(_maxIdle) < DateTime.UtcNow)) { Reset(); } return _startTime.AddTicks(_stopWatch.Elapsed.Ticks); } } private static void Reset() { _startTime = DateTime.UtcNow; _stopWatch = Stopwatch.StartNew(); } } 

Si restablece el temporizador híbrido a intervalos regulares (por ejemplo, cada hora o algo así), corre el riesgo de ajustar el tiempo antes de la última hora de lectura, como un problema en miniatura del horario de verano.

[La respuesta aceptada no parece ser segura para subprocesos y, según su propia admisión, puede retroceder en el tiempo provocando marcas de tiempo duplicadas, de ahí esta respuesta alternativa]

Si lo que realmente te importa (según tu comentario) es, de hecho, una marca de tiempo única asignada en estricto orden ascendente y que se corresponde lo más posible con la hora del sistema, puedes probar este enfoque alternativo:

 public class HiResDateTime { private static long lastTimeStamp = DateTime.UtcNow.Ticks; public static long UtcNowTicks { get { long orig, newval; do { orig = lastTimeStamp; long now = DateTime.UtcNow.Ticks; newval = Math.Max(now, orig + 1); } while (Interlocked.CompareExchange (ref lastTimeStamp, newval, orig) != orig); return newval; } } } 

Para obtener un conteo de ticks de alta resolución, use el método estático Stopwatch.GetTimestamp ():

 long tickCount = System.Diagnostics.Stopwatch.GetTimestamp(); DateTime highResDateTime = new DateTime(tickCount); 

solo eche un vistazo al Código fuente de .NET:

  public static long GetTimestamp() { if(IsHighResolution) { long timestamp = 0; SafeNativeMethods.QueryPerformanceCounter(out timestamp); return timestamp; } else { return DateTime.UtcNow.Ticks; } } 

Código fuente aquí: http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Stopwatch.cs,69c6c3137e12dab4

¡Todas estas sugerencias parecen demasiado difíciles! Si tiene Windows 8 o Server 2012 o superior, use GetSystemTimePreciseAsFileTime siguiente manera:

 [DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)] static extern void GetSystemTimePreciseAsFileTime(out long filetime); public DateTimeOffset GetNow() { long fileTime; GetSystemTimePreciseAsFileTime(out fileTime); return DateTimeOffset.FromFileTime(fileTime); } 

Esto tiene mucha, mucha mejor precisión que DateTime.Now sin ningún esfuerzo.

Consulte MSDN para obtener más información: http://msdn.microsoft.com/en-us/library/windows/desktop/hh706895(v=vs.85).aspx

Devuelve la fecha y hora más precisas que conoce el sistema operativo.

El sistema operativo también proporciona un tiempo de resolución más alto a través de QueryPerformanceCounter y QueryPerformanceFrequency (clase .NET Stopwatch ). Estos le permiten cronometrar un intervalo pero no le dan la fecha y la hora del día. Podría argumentar que estos podrían darle una hora y día muy precisos, pero no estoy seguro de cuánto se tuercen durante un largo intervalo.

1). Si necesita una precisión absoluta de alta resolución: no puede usar DateTime.Now cuando se basa en un reloj con un intervalo de 15 ms (a menos que sea posible “deslizar” la fase).

En cambio, una fuente externa de mejor resolución, el tiempo de precisión absoluta (por ejemplo, ntp), t1 continuación, podría combinarse con el temporizador de alta resolución (cronómetro / QueryPerformanceCounter).


2). Si solo necesitas alta resolución:

Muestra DateTime.Now ( t1 ) una vez junto con un valor del temporizador de alta resolución (StopWatch / QueryPerformanceCounter) ( tt0 ).

Si el valor actual del temporizador de alta resolución es tt entonces la hora actual, t , es:

 t = t1 + (tt - tt0) 

3). Una alternativa podría ser separar el tiempo absoluto y el orden de los eventos financieros: un valor para el tiempo absoluto (resolución de 15 ms, posiblemente desactivado por varios minutos) y un valor para el pedido (por ejemplo, incrementar un valor por uno cada vez y almacenar ese). El valor de inicio para la orden puede basarse en algún valor global del sistema o derivarse del tiempo absoluto cuando se inicia la aplicación.

Esta solución sería más robusta ya que no depende de la implementación del hardware subyacente de los relojes / temporizadores (que pueden variar entre los sistemas).

Esto es demasiado trabajo para algo tan simple. Simplemente inserte un DateTime en su base de datos con la operación. Luego, para obtener orden comercial, use su columna de identidad, que debería ser un valor creciente.

Si está insertando en múltiples bases de datos y tratando de reconciliarse después del hecho, estará calculando erróneamente la orden comercial debido a cualquier pequeña variación en los tiempos de su base de datos (incluso ns incrementos como usted dijo)

Para resolver el problema de la base de datos múltiple, podría exponer un solo servicio que cada operación realiza para obtener un pedido. El servicio podría devolver un DateTime.UtcNow.Ticks y un número comercial en aumento.

Incluso utilizando una de las soluciones anteriores, cualquier persona que realice operaciones desde una ubicación en la red con más latencia posiblemente coloque las transacciones en primer lugar (mundo real), pero se ingresan en el orden incorrecto debido a la latencia. Debido a esto, la operación debe considerarse ubicada en la base de datos, no en las consolas de los usuarios.

La precisión de 15 ms (en realidad puede ser de 15-25 ms) se basa en la resolución del temporizador de Windows 55 Hz / 65 Hz. Este es también el período básico de TimeSlice. Los afectados son Windows.Forms.Timer , Threading.Thread.Sleep , Threading.Timer , etc.

Para obtener intervalos precisos, debe usar la clase Cronómetro . Utilizará alta resolución si está disponible. Pruebe las siguientes declaraciones para averiguar:

 Console.WriteLine("H = {0}", System.Diagnostics.Stopwatch.IsHighResolution); Console.WriteLine("F = {0}", System.Diagnostics.Stopwatch.Frequency); Console.WriteLine("R = {0}", 1.0 /System.Diagnostics.Stopwatch.Frequency); 

Obtengo R = 6E-08 seg, o 60 ns. Debería ser suficiente para su propósito.

Si necesita la marca de tiempo para realizar pruebas comparativas, use el cronómetro que tiene una precisión mucho mejor que DateTime.Now.

Agregaría lo siguiente con respecto a MusiGenesis Answer para el tiempo de resincronización .

Significado: ¿A qué hora debo usar para volver a sincronizar (la _maxIdle en las respuestas de MusiGenesis )

Sabes que con esta solución no eres perfectamente preciso, por eso te vuelves a sincronizar. Pero también lo que implícitamente quieres es lo mismo que la solución de Ian Mercer :

una marca de tiempo única que se asigna en estricto orden ascendente

Por lo tanto, la cantidad de tiempo entre dos re-sync ( _maxIdle Lets lo llama SyncTime ) debe ser función de 4 cosas:

  • la resolución DateTime.UtcNow
  • la relación de precisión que desea
  • el nivel de precisión que deseas
  • Una estimación de la proporción fuera de sincronización de su máquina

Obviamente, la primera limitación en esta variable sería:

relación fuera de sincronización < = relación de precisión

Por ejemplo: no quiero que mi precisión sea peor que 0.5s / hrs o 1ms / day, etc. … (en mal inglés: no quiero estar más equivocado que 0.5s / hrs = 12s / day).

Por lo tanto, no puede lograr una precisión superior a la que le ofrece el cronómetro en su PC. Depende de su proporción fuera de sincronización, que puede no ser constante.

Otra restricción es el tiempo mínimo entre dos resincronizaciones:

Synctime> = DateTime.UtcNow resolución

Aquí la precisión y la precisión están vinculadas porque si usa una precisión alta (por ejemplo, para almacenar en un DB) pero con una precisión menor, puede romper la statement de Ian Mercer que es el orden ascendente estricto.

Nota: Parece que DateTime.UtcNow puede tener un res predeterminado mayor que 15ms (1ms en mi máquina) Siga el enlace: Alta precisión DateTime.UtcNow

Tomemos un ejemplo:

Imagine la proporción fuera de sincronización comentada anteriormente.

Después de aproximadamente 10 horas, estaba adelantado por 5 segundos.

Digamos que quiero una precisión microsec. Mi res temporizador es 1 ms (ver Nota anterior)

Entonces punto por punto:

  • la resolución DateTime.UtcNow : 1 ms
  • relación de precisión> = relación fuera de sincronización, permite tomar la más precisa posible así: relación de precisión = relación fuera de sincronización
  • el nivel de precisión que desea: 1 microsec
  • Una estimación de la relación fuera de sincronización de su máquina: 0.5s / hour (esta es también mi precisión)

Si restablece cada 10 segundos, imagínese que está en 9.999s, 1ms antes del reinicio. Aquí haces una llamada durante este intervalo. El tiempo que trazará su función está adelantado por: 0.5 / 3600 * 9.999s eq 1.39ms. Usted mostraría un tiempo de 10.000390 segundos. Después del tic de UtcNow, si haces una llamada dentro del 390micro sec, tendrás un número inferior al anterior. Es peor si esta relación fuera de sincronización es aleatoria dependiendo de la carga de la CPU u otras cosas.

Ahora digamos que puse SyncTime en su valor mínimo> I resync cada 1ms

Hacer el mismo pensamiento me pondría por delante en 0.139 microsec

Así que aquí la otra restricción es: ratio de precisión x SyncTime relación de precisión x SyncTime es bueno.

El problema está resuelto

Entonces, una recapitulación rápida sería:

  • Recupera tu resolución de temporizador.
  • Calcule una estimación de la proporción fuera de sincronización.
  • relación de precisión> = estimación de proporción fuera de sincronización, Mejor precisión = relación fuera de sincronización
  • Elija su nivel de precisión teniendo en cuenta lo siguiente:
  • timer-resolution < = SyncTime <= PrecisionLevel / (2 * accuracy-ratio)
  • La mejor precisión que puede lograr es la relación de resolución de temporizador * 2 * fuera de sincronización

Para la relación anterior (0.5 / h), el SyncTime correcto sería de 3.6ms, por lo que se redondearía a 3ms .

Con la relación anterior y la resolución del temporizador de 1 ms. Si desea un nivel de precisión de una marca (0.1microsec), necesita una proporción fuera de sincronización de no más de: 180ms / hora.

En su última respuesta a su propia respuesta, el estado MusiGenesis :

@Hermann: He estado ejecutando una prueba similar durante las últimas dos horas (sin la corrección de reinicio), y el temporizador basado en el cronómetro solo está funcionando unos 400 ms adelante después de 2 horas, por lo que el sesgo en sí parece ser variable (pero sigue siendo bastante severo). Estoy bastante sorprendido de que el sesgo sea tan malo; Supongo que esta es la razón por la que Stopwatch está en System.Diagnostics. – MusiGenesis

Por lo tanto, la precisión del Cronómetro es cercana a 200ms / hora, casi nuestra 180ms / hora. ¿Hay algún vínculo con por qué nuestro número y este número están tan cerca? No lo se. Pero esta precisión es suficiente para que logremos Tick-Precision

El mejor nivel de precisión: para el ejemplo anterior, es 0.27 microsec.

Sin embargo, qué sucede si lo llamo varias veces entre 9.999ms y la resincronización.

2 llamadas a la función podrían terminar con el mismo TimeStamp que se devuelve, el tiempo sería 9.999 para ambos (ya que no veo más precisión). Para evitar esto, no puede tocar el nivel de precisión porque está Vinculado a SyncTime por la relación anterior. Entonces debería implementar la solución de Ian Mercer para esos casos.

Por favor, no dudes en comentar mi respuesta.