2010-09-19 19 views
52

Notez que je vous demande quelque chose qui appellera une fonction de rappel plus d'une fois toutes les 15 ms en utilisant quelque chose comme System.Threading.Timer. Je ne demande pas comment chronométrer avec précision un morceau de code en utilisant quelque chose comme System.Diagnostics.Stopwatch ou même QueryPerformanceCounter.Pourquoi les temporisateurs .NET sont-ils limités à une résolution de 15 ms?

Aussi, j'ai lu les questions connexes:

Accurate Windows timer? System.Timers.Timer() is limited to 15 msec

High resolution timer in .NET

Ni qui fournit une réponse utile à ma question.

En outre, l'article MSDN recommandé, Implement a Continuously Updating, High-Resolution Time Provider for Windows, concerne la synchronisation des choses plutôt que de fournir un flux continu de ticks.

Cela dit. . .

Il existe beaucoup d'informations erronées sur les objets timer .NET. Par exemple, System.Timers.Timer est facturé comme "un temporisateur haute performance optimisé pour les applications serveur". Et System.Threading.Timer est en quelque sorte considéré comme un citoyen de deuxième classe. La sagesse conventionnelle est que System.Threading.Timer est un emballage autour de Windows Timer Queue Timers et que System.Timers.Timer est quelque chose d'autre entièrement.

La réalité est très différente. System.Timers.Timer est juste une enveloppe de composant mince autour de System.Threading.Timer (il suffit d'utiliser Reflector ou ILDASM pour jeter un coup d'oeil à System.Timers.Timer et vous verrez la référence à System.Threading.Timer), et a du code qui fournira une synchronisation automatique des threads de sorte que vous n'avez pas à le faire.

System.Threading.Timer, car il s'avère que n'est pas un wrapper pour les temporisateurs de file d'attente minuterie. Au moins pas dans le 2.0 runtime, qui a été utilisé à partir de .NET 2.0 à travers .NET 3.5. Quelques minutes avec l'interface de ligne de commande Shared Source montrent que le moteur d'exécution met en œuvre sa propre file d'attente de minuterie similaire aux minuteurs de la file d'attente de minuterie, mais n'appelle jamais les fonctions Win32.

Il semble que le runtime .NET 4.0 implémente également sa propre file d'attente de minuterie. Mon programme de test (voir ci-dessous) fournit des résultats similaires sous .NET 4.0 comme sous .NET 3.5. J'ai créé mon propre wrapper géré pour les minuteurs de file d'attente de minuterie et prouvé que je peux obtenir une résolution de 1 ms (avec une assez bonne précision), donc je considère qu'il est peu probable que je lis la source CLI erronée.

J'ai deux questions:

D'abord, ce qui provoque la mise en œuvre de la file d'attente de la minuterie de l'exécution pour être si lent? Je ne peux pas obtenir mieux que la résolution de 15 ms, et la précision semble être de l'ordre de -1 à +30 ms. Autrement dit, si je demande 24 ms, j'obtiendrai des tics de 23 à 54 ms. Je suppose que je pourrais passer plus de temps avec la source CLI pour trouver la réponse, mais je pensais que quelqu'un ici pourrait le savoir. Deuxièmement, et je me rends compte que c'est plus difficile à répondre, pourquoi ne pas utiliser les temporisateurs de file d'attente de minuterie? Je me suis rendu compte que .NET 1.x devait fonctionner sur Win9x, qui n'avait pas ces API, mais ils existaient depuis Windows 2000, et si je me souviens bien, c'était le minimum requis pour .NET 2.0. Est-ce parce que la CLI devait fonctionner sur des boîtes non-Windows?

Mon minuteries programme de test:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Threading; 

namespace TimerTest 
{ 
    class Program 
    { 
     const int TickFrequency = 5; 
     const int TestDuration = 15000; // 15 seconds 

     static void Main(string[] args) 
     { 
      // Create a list to hold the tick times 
      // The list is pre-allocated to prevent list resizing 
      // from slowing down the test. 
      List<double> tickTimes = new List<double>(2 * TestDuration/TickFrequency); 

      // Start a stopwatch so we can keep track of how long this takes. 
      Stopwatch Elapsed = Stopwatch.StartNew(); 

      // Create a timer that saves the elapsed time at each tick 
      Timer ticker = new Timer((s) => 
       { 
        tickTimes.Add(Elapsed.ElapsedMilliseconds); 
       }, null, 0, TickFrequency); 

      // Wait for the test to complete 
      Thread.Sleep(TestDuration); 

      // Destroy the timer and stop the stopwatch 
      ticker.Dispose(); 
      Elapsed.Stop(); 

      // Now let's analyze the results 
      Console.WriteLine("{0:N0} ticks in {1:N0} milliseconds", tickTimes.Count, Elapsed.ElapsedMilliseconds); 
      Console.WriteLine("Average tick frequency = {0:N2} ms", (double)Elapsed.ElapsedMilliseconds/tickTimes.Count); 

      // Compute min and max deviation from requested frequency 
      double minDiff = double.MaxValue; 
      double maxDiff = double.MinValue; 
      for (int i = 1; i < tickTimes.Count; ++i) 
      { 
       double diff = (tickTimes[i] - tickTimes[i - 1]) - TickFrequency; 
       minDiff = Math.Min(diff, minDiff); 
       maxDiff = Math.Max(diff, maxDiff); 
      } 

      Console.WriteLine("min diff = {0:N4} ms", minDiff); 
      Console.WriteLine("max diff = {0:N4} ms", maxDiff); 

      Console.WriteLine("Test complete. Press Enter."); 
      Console.ReadLine(); 
     } 
    } 
} 
+0

Bonne question! Je suis intéressé si quelqu'un a vraiment un aperçu de cela. Quelqu'un de l'équipe clr sur SO? –

+0

Je parie que win9x avait aussi des tics dans le noyau. c'était un environnement de travail préemptif après tout. tout est merdique mais toujours vrai multitâche. donc vous ne pouvez pas faire de préemption sans interruptions, et ils ont tendance à tirer à partir d'une minuterie de matériel ordinaire, en particulier dans les années 90, basé sur le HTC (64 Hz). Comment faites-vous l'interface graphique sans une file d'attente d'événements? Les événements de minuterie sont poussés vers la file d'attente d'événements par le noyau, il n'y a aucun moyen de contourner le problème, car pendant que votre application n'attend pas la file d'attente, elle est dé-programmée. –

Répondre

27

Peut-être le document lié ici explique un peu. Il est un peu sec donc je ne parcouru it :) rapidement

Citant l'intro:

La résolution de la minuterie du système détermine la fréquence Windows effectue deux actions principales:

  • Mettre à jour la tique minuterie compte si une coche complète s'est écoulée.
  • Vérifiez si un objet de minuteur planifié a expiré.

Un timer tick est une notion de temps écoulé que Windows utilise pour suivre le temps du jour et du fil quantique fois. Par défaut, l'interruption d'horloge et tick tick sont les mêmes, mais Windows ou une application peut changer l'horloge période d'interruption.

La résolution de temporisateur par défaut sur Windows 7 est de 15,6 millisecondes (ms). Certaines applications réduisent cette durée à 1 ms, ce qui réduit le temps d'exécution de la batterie de sur les systèmes mobiles de jusqu'à 25%.

Originaire de: Timers, Timer Resolution, and Development of Efficient Code (docx).

+0

Merci pour le lien, Arnold. Cela ne répond pas entièrement à ma question, mais il donne un aperçu, en particulier le bit au sujet de la résolution de minuterie par défaut de 15,6 ms. –

+0

Oui, je n'ai pas eu l'impression que tout m'expliquait mais j'avais des infos intéressantes. –

+0

Voir aussi la réponse dans le fil de discussion ["comment régler la résolution du minuteur de C# à 1 ms?"] (Http://stackoverflow.com/questions/15071359). –

13

La résolution de la minuterie est donnée par le battement de coeur du système. Cela revient par défaut à 64 battements/s, soit 15,625 ms. Cependant, il existe des moyens de modifier ces systèmes paramètres généraux pour atteindre des résolutions de la minuterie jusqu'à 1 ms ou même à 0,5 ms sur les plates-formes plus récentes:

1. Objectif 1 ms résolution au moyen de l'interface de minuterie multimédia:

L'interface du temporisateur multimédia est capable de fournir une résolution de 1 ms. Voir About Multimedia Timers (MSDN), Obtaining and Setting Timer Resolution (MSDN) et this pour plus d'informations sur timeBeginPeriod. Remarque: N'oubliez pas d'appeler le timeEndPeriod pour revenir à la résolution de la minuterie par défaut lorsque vous avez terminé.

Comment faire:

#define TARGET_RESOLUTION 1   // 1-millisecond target resolution 

TIMECAPS tc; 
UINT  wTimerRes; 

if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) 
{ 
    // Error; application can't continue. 
} 

wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax); 
timeBeginPeriod(wTimerRes); 

//  do your stuff here at approx. 1 ms timer resolution 

timeEndPeriod(wTimerRes); 

Note: Cette procédure est availble à d'autres processus aussi bien et la résolution obtenue applique l'ensemble du système. La plus haute résolution demandée par n'importe quel processus sera active, attention aux conséquences.

2. Aller à 0,5 ms Résolution:

Vous pouvez obtenir 0,5 ms résolution au moyen de l'API caché NtSetTimerResolution(). NtSetTimerResolution est exportée par la bibliothèque Windows NT native NTDLL.DLL. Voir How to set timer resolution to 0.5ms ? sur MSDN. Néanmoins, la vraie résolution réalisable est déterminée par le matériel sous-jacent. Le matériel moderne prend en charge une résolution de 0,5 ms. Encore plus de détails sont trouvés dans Inside Windows NT High Resolution Timers. Les résolutions prises en charge peuvent être obtenues par un appel à NtQueryTimerResolution().

Comment faire:

#define STATUS_SUCCESS 0 
#define STATUS_TIMER_RESOLUTION_NOT_SET 0xC0000245 

// after loading NtSetTimerResolution from ntdll.dll: 

// The requested resolution in 100 ns units: 
ULONG DesiredResolution = 5000; 
// Note: The supported resolutions can be obtained by a call to NtQueryTimerResolution() 

ULONG CurrentResolution = 0; 

// 1. Requesting a higher resolution 
// Note: This call is similar to timeBeginPeriod. 
// However, it to to specify the resolution in 100 ns units. 
if (NtSetTimerResolution(DesiredResolution ,TRUE,&CurrentResolution) != STATUS_SUCCESS) { 
    // The call has failed 
} 

printf("CurrentResolution [100 ns units]: %d\n",CurrentResolution); 
// this will show 5000 on more modern platforms (0.5ms!) 

//  do your stuff here at 0.5 ms timer resolution 

// 2. Releasing the requested resolution 
// Note: This call is similar to timeEndPeriod 
switch (NtSetTimerResolution(DesiredResolution ,FALSE,&CurrentResolution) { 
    case STATUS_SUCCESS: 
     printf("The current resolution has returned to %d [100 ns units]\n",CurrentResolution); 
     break; 
    case STATUS_TIMER_RESOLUTION_NOT_SET: 
     printf("The requested resolution was not set\n"); 
     // the resolution can only return to a previous value by means of FALSE 
     // when the current resolution was set by this application  
     break; 
    default: 
     // The call has failed 

} 

Note: La fonctionnalité de NtSetTImerResolution est essentiellement mis en correspondance avec les fonctions timeBeginPeriodettimeEndPeriod en utilisant la valeur bool Set (voir Inside Windows NT High Resolution Timers pour plus de détails sur le système et toutes ses implications). Cependant, la suite multimédia limite la granularité en millisecondes et NtSetTimerResolution permet de définir des valeurs inférieures à la milliseconde.

+0

Basé sur mes expériences, je pense que le code d'erreur STATUS_TIMER_RESOLUTION_NOT_SET doit être 0xC0000245 pas 245 –

+0

@RolandPihlakas C'est correct, merci. J'ai édité ma réponse pour que cela soit corrigé. – Arno