2008-11-21 9 views
25

J'ai le code en cours d'exécution dans une boucle et il enregistre l'état en fonction de l'heure actuelle. Parfois, cela peut être séparé de quelques millisecondes, mais pour une raison quelconque, il semble que DateTime.Now renvoie toujours des valeurs d'au moins 10 ms, même si ce n'est que 2 ou 3 ms plus tard. Cela pose un problème majeur depuis l'état où je suis sauver dépend du temps qu'il a été enregistré (par exemple, l'enregistrement de quelque chose)À quelle fréquence DateTime.Now est-il mis à jour? ou existe-t-il une API plus précise pour obtenir l'heure actuelle?

Mon code de test qui retourne 10 ms d'intervalle chaque valeur:

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

Y at-il une autre solution sur comment obtenir l'heure actuelle précise jusqu'à la milliseconde?

Quelqu'un a suggéré de regarder la classe Stopwatch. Bien que la classe Stopwatch soit très précise, elle ne me dit pas l'heure actuelle, ce dont j'ai besoin pour sauvegarder l'état de mon programme.

+1

Toute personne venant ici, le TL; DR http://msdn.microsoft.com/en-us/library/ms644904%28VS.85%29.aspx – AnotherUser

Répondre

-3

Vous pouvez utiliser DateTime.Now.Ticks, lire le artical sur MSDN

« Une seule tique représente une centaine de nanosecondes ou un dix-millionième de seconde. »

+0

Looks prometteur, je vais vérifier. –

+7

D'où pensez-vous que les tiques viennent? Vous aurez toujours le retard dans la mesure datetime, vous avez juste la mesure inexacte en ticks. Toutes les propriétés ticks renvoie une représentation en tick de la valeur datetime. vous aurez toujours des problèmes de précision. – RhysC

2

Si vous prenez une photo instantanée de l'heure actuelle avant de faire quoi que ce soit, vous pouvez simplement ajouter le chronomètre au temps que vous avez enregistré, non?

+0

Une pensée intéressante. C'est un code très performant et je ne suis pas sûr que l'utilisation de compteurs de performance (Stopwatch internal API) soit une bonne idée. Je vais vérifier ça. –

+0

J'ai trouvé dans mon benchmark (d'un système plus grand) qu'il n'y a pas de différence notable entre l'utilisation de DateTime.UtcNow ou l'ajout d'une valeur Stopwatch à une heure de base commune dans un million d'appels. – ygoe

1

Vous devriez vous demander si vous avez vraiment besoin de temps précis, ou juste assez de temps plus un entier croissant. Vous pouvez faire de bonnes choses en obtenant now() juste après un événement d'attente tel qu'un mutex, select, poll, WaitFor *, etc., et en ajoutant un numéro de série à cela, peut-être dans la plage des nanosecondes ou partout est la pièce. Vous pouvez également utiliser l'instruction rdtsc machine (certaines bibliothèques fournissent un wrapper d'API pour cela, ne sont pas sûr de le faire en C# ou en Java) pour obtenir un temps réduit du CPU et le combiner avec time(). Le problème avec rdtsc est que sur les systèmes avec une mise à l'échelle rapide, vous ne pouvez jamais être sûr de ce que cela va faire. Il s'enroule aussi assez rapidement.

+0

Mon problème est que j'ai besoin de rejouer l'état enregistré. Donc, si je sauvegarde deux états à 5 ms d'intervalle, je dois pouvoir les rejouer à 5 ms l'un de l'autre. Juste connaître l'ordre ne suffit pas. –

+0

Je ne connais pas le problème que vous essayez de résoudre, mais vous essayez peut-être trop dur. Juste obtenir la commande juste dans la granularité de 10 ms peut être suffisant. –

+0

Ou peut-être ressemble à une tâche qui devrait être accélérée matériellement ou implémentée dans un environnement en temps réel. – binki

10

Le problème avec DateTime lors du traitement de millisecondes n'est pas du tout dû à la classe DateTime, mais concerne plutôt les graduations de processeur et de thread. Essentiellement, lorsqu'une opération est mise en pause par le planificateur pour permettre à d'autres threads de s'exécuter, elle doit attendre au moins une tranche de temps avant de reprendre, ce qui représente environ 15 ms sur les systèmes d'exploitation Windows modernes. Par conséquent, toute tentative de pause inférieure à cette précision de 15 ms entraînera des résultats inattendus.

+4

Erm, le problème est que DateTime.Now a une résolution de 10 millisecondes ... Voir http://msdn.microsoft.com/en-us/library/system.datetime.now.aspx – configurator

+7

Et où pensez-vous que Une restriction de 10 millisecondes vient? Astuce: Ce n'est pas arbitraire. – Chris

40

Curieusement, votre code fonctionne parfaitement sur mon quad core sous Win7, générant des valeurs exactement à 2 ms à peu près à chaque fois. J'ai donc fait un test plus approfondi. Voici mon exemple de sortie pour Thread.Sleep(1). Le code imprime le nombre de ms entre des appels consécutifs à DateTime.UtcNow dans une boucle:

sleep 1

Chaque ligne contient 100 caractères, et représente donc 100 ms de temps sur un « run propre ». Donc, cet écran couvre environ 2 secondes. La plus longue préemption était de 4ms; de plus, il y avait une période d'environ 1 seconde lorsque chaque itération prenait exactement 1 ms. C'est presque la qualité du système d'exploitation en temps réel! :)

donc j'ai essayé à nouveau, avec cette fois-ci Thread.Sleep(2):

sleep 2

Encore une fois, des résultats presque parfaits. Cette fois-ci, chaque rangée mesure 200ms de long, et il y a une course de près de 3 secondes où l'écart n'a jamais été que de 2ms exactement.

Naturellement, la prochaine chose à voir est la résolution réelle de DateTime.UtcNow sur ma machine. Voici une course sans sommeil du tout; un . est imprimé si UtcNow n'a pas changé du tout :

no sleep

Enfin, alors qu'il enquêtait sur une affaire étrange horodatages étant 15ms en dehors sur la même machine qui a produit les résultats ci-dessus, je n'ai plus dans les événements curieux suivants:

enter image description here enter image description here

Il y a une fonction dans l'API Windows appelé timeBeginPeriod, quelles applications peuvent utiliser pour augmenter temporairement la fréquence du minuteur, donc c'est probablement ce qui s'est passé ici. La documentation détaillée de la résolution de minuterie est disponible via le Hardware Dev Center Archive, en particulier Timer-Resolution.docx (un fichier Word).

Conclusions:

  • DateTime.UtcNowpeut une résolution beaucoup plus élevée que 15ms
  • Thread.Sleep(1)peut dormir exactement 1ms
  • Sur ma machine, UtcNow pousse croître de exactement 1ms à un moment (donner ou prendre une erreur d'arrondi - Reflector montre qu'il y a une division dans).
  • Il est possible que le processus bascule en mode basse résolution, quand tout est basé sur 15,6 ms, et un mode haute résolution, avec des tranches de 1 ms, à la volée.

Voici le code:

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

Il se trouve, il existe une fonction non documentée qui peut modifier la résolution de la minuterie. Je n'ai pas étudié les détails, mais j'ai pensé que je posterais un lien ici: NtSetTimerResolution.

Bien sûr, j'ai fait en sorte que le système d'exploitation soit aussi inactif que possible, et il dispose de quatre cœurs de processeur assez puissants. Si je charge tous les quatre cœurs à 100%, l'image change complètement, avec de longues préemptions partout.

+4

Ces deux dernières phrases sont plutôt importantes :-) – nos

+3

Il existe des fonctions documentées qui modifient également la résolution du minuteur. Ces méthodes sont [timePeriodBegin] (http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624%28v=vs.85%29.aspx) et [timePeriodEnd] (http: // msdn. microsoft.com/en-us/library/windows/desktop/dd757626%28v=vs.85%29.aspx) – Chuu

0

Tout ce que j'ai utilisé pour accomplir cette tâche à 100% avec précision est un contrôle de minuterie et une étiquette.

Le code n'exige pas beaucoup d'explications, assez simple. variables globales:

int timer = 0; 

Ceci est l'événement tique:

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


} 

Voici la charge de forme:

private void DriverAssignment_Load(object sender, EventArgs e) 
    { 


     timeOfDay.Interval= 1; 
     timeOfDay.Enabled = true; 


} 
0

Répondre à la deuxième partie de votre question au sujet d'une API plus précise, le comment de AnotherUser me conduire à cette solution qui dans mon scénario surmonte le problème de précision 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); 

Dans mes propres repères, en itérant 1M, il renvoie en moyenne 3 ticks vs DateTime.Now 2 ticks.

Pourquoi 1600 est hors de ma juridiction, mais je l'utilise pour obtenir la bonne année.

EDIT: C'est toujours un problème sur win10. Toute personne intéressée peut exécuter cette paix de la preuve:

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