2011-04-10 14 views
26

Estoy usando marcas de tiempo para ordenar temporalmente cambios simultáneos en mi programa, y ​​requiero que cada marca de tiempo de un cambio sea única. Sin embargo, descubrí que simplemente llamar a DateTime.Now es insuficiente, ya que a menudo devolverá el mismo valor si se llama en sucesión rápida.¿Cómo asegurar que una marca de tiempo sea siempre única?

Tengo algunas ideas, pero nada me parece la mejor solución para esto. ¿Hay algún método que pueda escribir que garantice que cada llamada sucesiva produzca un único DateTime?

¿Debo tal vez utilizar un tipo diferente para esto, tal vez una int larga? DateTime tiene la ventaja obvia de ser fácilmente interpretable como un tiempo real, a diferencia de, por ejemplo, un contador incremental.

Actualización: Esto es lo que acabamos de codificación como una solución de compromiso simple que todavía me permite usar DateTime como mi clave temporal, al tiempo que garantiza la unicidad cada vez que el método se llama:

private static long _lastTime; // records the 64-bit tick value of the last time 
private static object _timeLock = new object(); 

internal static DateTime GetCurrentTime() { 
    lock (_timeLock) { // prevent concurrent access to ensure uniqueness 
     DateTime result = DateTime.UtcNow; 
     if (result.Ticks <= _lastTime) 
      result = new DateTime(_lastTime + 1); 
     _lastTime = result.Ticks; 
     return result; 
    } 
} 

Debido a que cada el valor tick es solo una 10 millonésima de segundo, este método solo introduce un obvio sesgo en el reloj cuando se lo llama del orden de 10 millones de veces por segundo (que, por cierto, es lo suficientemente eficiente para ejecutarse en), lo que significa que es perfectamente aceptable para mis propósitos

Aquí hay un código de prueba:

DateTime start = DateTime.UtcNow; 
DateTime prev = Kernel.GetCurrentTime(); 
Debug.WriteLine("Start time : " + start.TimeOfDay); 
Debug.WriteLine("Start value: " + prev.TimeOfDay); 
for (int i = 0; i < 10000000; i++) { 
    var now = Kernel.GetCurrentTime(); 
    Debug.Assert(now > prev); // no failures here! 
    prev = now; 
} 
DateTime end = DateTime.UtcNow; 
Debug.WriteLine("End time: " + end.TimeOfDay); 
Debug.WriteLine("End value: " + prev.TimeOfDay); 
Debug.WriteLine("Skew:  " + (prev - end)); 
Debug.WriteLine("GetCurrentTime test completed in: " + (end - start)); 

... y los resultados:

Start time: 15:44:07.3405024 
Start value: 15:44:07.3405024 
End time: 15:44:07.8355307 
End value: 15:44:08.3417124 
Skew:  00:00:00.5061817 
GetCurrentTime test completed in: 00:00:00.4950283 

Así, en otras palabras, en el medio segundo que genera 10 millones de únicas marcas de tiempo, y el resultado final solo fue adelantado en medio segundo. En aplicaciones del mundo real, el sesgo sería imperceptible.

+0

¿La aplicación tiene varios subprocesos? ¿El significado puede dos hilos diferentes pedir una identificación al mismo tiempo? –

+0

Sí ............ – devios1

+0

¿Hay algún motivo por el que no pueda usar las guías? – juharr

Respuesta

57

Una forma de obtener una secuencia estrictamente ascendente de marcas de tiempo sin duplicados es el siguiente código.

En comparación con las otras respuestas aquí éste tiene los siguientes beneficios:

  1. Los valores siguen de cerca con los valores reales en tiempo real (excepto en circunstancias extremas, con muy altas tasas de petición cuando se obtendrían ligeramente por delante de tiempo real).
  2. No tiene bloqueos y debe tener un mejor rendimiento que las soluciones con las declaraciones lock.
  3. Garantiza el orden ascendente (simplemente anexar un bucle no lo hace un contador).

.

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

      return newValue; 
     } 
    } 
} 
+1

+1. Ejecuté algunas pruebas unitarias contra este código. De hecho, funciona, y funciona marginalmente más rápido que cualquier otra solución. El único inconveniente es que no es intuitivo para el lector promedio. Me gustaría dedicar más tiempo a explicar cómo funciona para los demás de lo que prefiero frente a un simple bloqueo (que es bastante rápido). [Complejidad/equilibrio de rendimiento]. Pero de nuevo, votaría su solución más alto si pudiera. –

+0

Me pareció fácil de entender y bien escrito, a excepción de los nombres de las variables, que podrían ser palabras en inglés. ¡Muchas gracias! –

+0

@FrankHileman, nombres de variables actualizados según su sugerencia, ¡gracias! –

1

No se puede garantizar que sea único, pero tal vez esté usando ticks es lo suficientemente granular.

Una sola garrapata representa cien nanosegundos o una diez millonésima parte de un segundo . Hay 10,000 ticks en un en milisegundos.

+1

Desafortunadamente, parece que DateTime.Now solo se actualiza cada 15 ms aproximadamente, lo que hace que la propiedad Ticks no tenga sentido en este escenario. – devios1

+1

Hay una técnica API que implica GetTickCount() para obtener lo que desea. – Joshua

6

DateTime.Now solo se actualiza cada 10-15ms.

No es una víctima de por sí, pero este hilo tiene algunas ideas sobre la reducción de los duplicados/proporcionando una mejor resolución de tiempo:

How to get timestamp of tick precision in .NET/C#?

Dicho esto: las marcas de tiempo son claves horribles de información; Si las cosas suceden tan rápido, es posible que desee un índice/contador que mantenga el orden discreto de los elementos a medida que ocurren. No hay ambigüedad allí.

7

Er, la respuesta a su pregunta es que "no se puede", ya que si dos operaciones ocurren al mismo tiempo (que sucederán en procesadores multi-core), tendrán la misma marca de tiempo, sin importar qué precisión que logra reunir

Dicho esto, parece que lo que quiere es algún tipo de contador de subprocesos automático de subprocesos.Para implementar esto (presumiblemente como un servicio global, quizás en una clase estática), usaría el método Interlocked.Increment, y si decidió que necesitaba más de int.MaxValue posibles versiones, también Interlocked.Read.

0

No estoy seguro de lo que está tratando de hacer en su totalidad, pero tal vez investigue el uso de colas para manejar registros de proceso secuencialmente.

2

Me parece que la forma más infalible es combinar una marca de tiempo y un contador atómico. Ya conoce el problema con la pobre resolución de una marca de tiempo. Usar un contador atómico por sí solo también tiene el simple problema de requerir que se almacene su estado si va a detener e iniciar la aplicación (de lo contrario, el contador volverá a comenzar en 0, causando duplicados).

Si solo fuera por una identificación única, sería tan simple como concatenar la marca de tiempo y el valor del contador con un delimitador entre. Pero como quiera que los valores estén siempre en orden, eso no será suficiente. Básicamente, todo lo que necesita hacer es usar el valor del contador atómico para agregar precisión adicional de ancho fijo a su marca de tiempo. Soy desarrollador de Java, por lo que aún no podré proporcionar el código de muestra de C#, pero el problema es el mismo en ambos dominios. Así que solo siga estos pasos generales:

  1. Necesitará un método para proporcionarle valores de contador ciclando de 0-99999. 100000 es el número máximo de valores posible al concatenar una marca de tiempo de precisión de milisegundos con un valor de ancho fijo en una longitud de 64 bits. Entonces, básicamente está asumiendo que nunca necesitará más de 100000 identificadores dentro de una única resolución de marca de tiempo (15 ms o menos). Un método estático, utilizando la clase Interlocked para proporcionar incrementos atómicos y restablecer a 0 es la forma ideal.
  2. Ahora para generar su id simplemente concatena su marca de tiempo con su valor de contador rellenado a 5 caracteres. Así que si su marca de tiempo era 1302399107y su contador estaba en 234 el id sería 1302399107.

Esta estrategia va a funcionar todo el tiempo que no está necesitando más rápido que los identificadores de 6666 por ms (15 ms es asumir su resolución más granular) y siempre funcionará sin tener que guardar ningún estado en los reinicios de su aplicación.

+0

Esto se siente como el mejor compromiso. Estaba pensando en algo así como una posible solución; gracias por entrar en detalles Creo que 100.000 cambios por 15 ms es un límite bastante aceptable. :) – devios1

+0

También podría representar el valor como una cadena, en lugar de un int de 64 bits, y simplemente concatenar un contador de longitud arbitraria al final de la cadena (quizás con un delimitador como '.'), Y así evitar el 6666 por ms "limitación". Uno podría simplemente cortar esto del final para interpretar la parte inicial como una fecha de nuevo para la legibilidad. – devios1

+0

Sí, pero luego debe preocuparse por ordenar. '" 123456789.12345 "<" 123456789.234 "' si se compara de forma natural. Ambas partes tienen que ser de ancho fijo. Aunque sin duda podría aumentar el ancho de la segunda parte. Estaba contando con el hecho de que el valor de los millis por el tiempo no aumentará en dígitos durante mucho tiempo. En el año 2286 las personas estarán furiosas de que lo hayas hecho. –

Cuestiones relacionadas