web-dev-qa-db-fra.com

Comment s'assurer que l'horodatage est toujours unique?

J'utilise des horodatages pour ordonner temporairement les modifications simultanées de mon programme et exige que chaque horodatage d'un changement soit unique. Cependant, j'ai découvert qu'appeler simplement DateTime.Now est insuffisant, car il retournera souvent la même valeur s'il est appelé successivement.

J'ai quelques idées, mais rien ne me semble être la "meilleure" solution à ce problème. Existe-t-il une méthode que je peux écrire qui garantisse que chaque appel successif produit une DateTime unique?

Devrais-je peut-être utiliser un type différent pour cela, peut-être un long int? DateTime a l'avantage évident d'être facilement interprétable en temps réel, contrairement à un compteur incrémentiel.

Mise à jour: Voici ce que j'ai fini par coder comme une solution de compromis simple qui me permet encore d'utiliser DateTime comme clé temporelle, tout en garantissant l'unicité à chaque appel de la méthode:

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;
    }
}

Comme chaque valeur de tick ne représente qu'un 10 millionième de seconde, cette méthode introduit uniquement un décalage d'horloge notable lorsqu'elle est appelée de l'ordre de 10 millions de fois par seconde (ce qui est d'ailleurs suffisamment efficace pour s'exécuter à), ce qui signifie parfaitement acceptable pour mes besoins.

Voici un code de test:

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 ) );

... et les résultats:

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

En d’autres termes, en une demi-seconde, il a généré 10 millions unique / horodatage et le résultat final n’a été avancé que d’une demi-seconde. Dans les applications réelles, l'inclinaison serait imperceptible.

29
devios1

Le code suivant constitue un moyen d'obtenir une séquence d'horodatage strictement ascendante sans doublons. 

Comparé aux autres réponses ici, celui-ci présente les avantages suivants:

  1. Les valeurs suivent étroitement les valeurs en temps réel réelles (sauf dans des circonstances extrêmes avec des taux de demande très élevés où elles deviendraient légèrement plus rapides que le temps réel).
  2. Il n’est pas verrouillé et devrait être plus performant que les solutions utilisant les instructions lock.
  3. Cela garantit un ordre croissant (le simple fait d'ajouter une boucle à un compteur ne le fait pas).

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;
       }
   }
}
68
Ian Mercer

Euh, la réponse à votre question est que "vous ne pouvez pas", car si deux opérations sont effectuées en même temps (ce qui sera le cas dans les processeurs multicœurs), elles auront le même horodatage, quelle que soit la précision que vous parviendrez à obtenir. recueillir.

Cela dit, cela ressemble à ce que vous voulez, c'est une sorte de compteur thread-safe auto-incrémenté. Pour implémenter cela (probablement en tant que service global, peut-être dans une classe statique), vous utiliseriez la méthode Interlocked.Increment , et si vous décidiez de prendre plus de int.MaxValue versions possibles, également Interlocked.Read .

7
Domenic

DateTime.Now n'est mis à jour que toutes les 10-15ms. 

Ce n'est pas une dupe en soi, mais ce fil a quelques idées sur la réduction des doublons/fournissant une meilleure résolution temporelle:

Comment obtenir l'horodatage de la précision des ticks dans .NET/C #?

Cela étant dit: les horodatages sont des clés horribles pour l’information; si les choses se passent aussi vite, vous voudrez peut-être un index/compteur qui garde l'ordre discret des éléments au fur et à mesure de leur apparition. Il n'y a pas d'ambiguïté là-bas.

6
Joe

Je trouve que le moyen le plus sûr est de combiner un horodatage et un compteur atomique. Vous connaissez déjà le problème de la mauvaise résolution d'un horodatage. L’utilisation d’un compteur atomique à lui seul pose également le simple problème de demander que son état soit stocké si vous voulez arrêter et démarrer l’application (sinon, le compteur recommence à 0, ce qui entraîne des doublons). 

Si vous ne recherchiez qu'un identifiant unique, ce serait aussi simple que de concaténer la valeur de l'horodatage et du compteur avec un séparateur. Mais comme vous voulez que les valeurs soient toujours en ordre, cela ne suffira pas. Fondamentalement, tout ce que vous avez à faire est d’utiliser la valeur du compteur atomique pour ajouter une précision supplémentaire de largeur fixe à votre horodatage. Je suis un développeur Java, donc je ne pourrai pas encore fournir d’exemple de code C #, mais le problème est le même dans les deux domaines. Il suffit donc de suivre ces étapes générales:

  1. Vous aurez besoin d’une méthode pour vous fournir des valeurs de compteur allant de 0 à 99999. 100000 est le nombre maximum de valeurs possibles lors de la concaténation d'un horodatage de précision milliseconde avec une valeur de largeur fixe de 64 bits. Vous supposez donc que vous n’aurez jamais besoin de plus de 100 000 identifiants avec une résolution d’horodatage unique (environ 15ms). Une méthode statique, utilisant la classe Interlocked pour fournir une incrémentation atomique et une réinitialisation à 0, constitue le moyen idéal.
  2. Maintenant, pour générer votre identifiant, il vous suffit de concaténer votre horodatage avec la valeur de votre compteur complétée à 5 caractères. Donc, si votre horodatage était 13023991070123 et que votre compteur était à 234, l'id serait 1302399107012300234. 

Cette stratégie fonctionne tant que vous n'avez pas besoin d'identifiants plus rapides que 6666 par ms (en supposant que la résolution la plus granulaire soit 15ms) et fonctionne toujours sans qu'il soit nécessaire de sauvegarder un état lors des redémarrages de votre application.

2
Justin Waugh

Il ne peut pas être garanti d'être unique, mais peut-être que l'utilisation de ticks est suffisamment granulaire?

Un tick simple représente cent Nanosecondes ou un dix millionième de Seconde. Il y a 10 000 ticks en Milliseconde.

1
Giovanni Galbo

Vous ne savez pas trop ce que vous essayez de faire, mais envisagez peut-être d'utiliser des files d'attente pour traiter les enregistrements de manière séquentielle.

0
mservidio