2010-09-19 8 views
52

Beachten Sie, dass ich nach etwas frage, das eine Callback-Funktion öfter als einmal alle 15 ms aufrufen wird, indem Sie etwas wie System.Threading.Timer verwenden. Ich frage nicht, wie man ein Stück Code mit etwas wie System.Diagnostics.Stopwatch oder sogar QueryPerformanceCounter genau zeitlich einstellt.Warum sind .NET-Timer auf 15 ms begrenzt?

Außerdem habe ich die damit verbundenen Fragen lauten:

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

High resolution timer in .NET

Keiner von denen liefert eine nützliche Antwort auf meine Frage.

Darüber hinaus ist der empfohlene MSDN-Artikel, Implement a Continuously Updating, High-Resolution Time Provider for Windows, über Dinge zu timing, anstatt einen kontinuierlichen Strom von Ticks bereitzustellen.

Mit dem gesagt. . .

Es gibt eine ganze Reihe schlechter Informationen über die .NET-Timer-Objekte. Zum Beispiel wird System.Timers.Timer als "ein Hochleistungs-Timer für Server-Anwendungen optimiert" berechnet. Und System.Threading.Timer wird irgendwie als Bürger zweiter Klasse betrachtet. Die gängige Meinung ist, dass System.Threading.Timer ein Wrapper um Windows Timer Queue Timers ist und dass System.Timers.Timer etwas ganz anderes ist.

Die Realität ist viel anders. System.Timers.Timer ist nur eine dünne Komponente Wrapper um System.Threading.Timer (verwenden Sie einfach Reflektor oder ILDASM in System.Timers.Timer zu sehen und Sie werden den Verweis auf System.Threading.Timer sehen), und hat einige Code, der automatische Thread-Synchronisierung bieten wird, so dass Sie es nicht tun müssen.

System.Threading.Timer, wie sich herausstellt ist nicht ein Wrapper für die Timer-Warteschlangen-Timer. Zumindest nicht in der 2.0-Laufzeit, die von .NET 2.0 bis .NET 3.5 verwendet wurde. Ein paar Minuten mit der Shared Source-CLI zeigt, dass die Laufzeitumgebung eine eigene Timer-Warteschlange implementiert, die den Timer-Warteschlangen-Timern ähnelt, die Win32-Funktionen jedoch nie aufruft.

Es scheint, dass die .NET 4.0-Laufzeit auch eine eigene Timer-Warteschlange implementiert. Mein Testprogramm (siehe unten) liefert unter .NET 4.0 ähnliche Ergebnisse wie unter .NET 3.5. Ich habe meinen eigenen verwalteten Wrapper für die Timer Queue Timer erstellt und bewiesen, dass ich 1 ms Auflösung bekommen kann (mit ziemlich guter Genauigkeit), daher halte ich es für unwahrscheinlich, dass ich die CLI-Quelle falsch lese.

Ich habe zwei Fragen:

Erstens, was die Umsetzung der Timer-Warteschlange Laufzeit verursacht so langsam sein? Ich kann nicht besser als 15 ms Auflösung, und die Genauigkeit scheint im Bereich von -1 bis +30 ms zu sein. Das heißt, wenn ich nach 24 ms frage, bekomme ich Zecken irgendwo zwischen 23 und 54 ms. Ich nehme an, ich könnte etwas mehr Zeit mit der CLI-Quelle verbringen, um die Antwort aufzuspüren, aber ich dachte, jemand könnte es wissen.

Zweitens, und ich merke, dass dies schwieriger zu beantworten ist, warum nicht die Timer-Warteschlange-Timer verwenden? Ich weiß, dass .NET 1.x auf Win9x laufen musste, das diese APIs nicht hatte, aber sie haben seit Windows 2000 existiert, was, wenn ich mich richtig erinnere, die Mindestvoraussetzung für .NET 2.0 war. Liegt es daran, dass das CLI auf Nicht-Windows-Boxen ausgeführt werden musste?

Meine Timer Testprogramm:

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

Gute Frage! Ich bin interessiert, wenn jemand tatsächlich Einblick in das hat. Jeder von der Clr-Team auf SO? –

+0

Ich würde wetten, dass win9x auch Ticks im Kernel hatte. es war schließlich eine präventive Tasking-Umgebung. alles beschissen, aber immer noch echt Multitasking. So können Sie keine Unterbrechungen ohne Unterbrechungen machen, und sie neigen dazu, von einem normalen Hardware-Timer zu feuern, besonders in den 90er Jahren, basierend auf dem HTC (64 Hz). Wie machen Sie GUI ohne eine Ereigniswarteschlange? Zeitgeberereignisse werden vom Kernel an die Ereigniswarteschlange gesendet. Es gibt keinen anderen Weg, denn während Ihre App nichts auf die Warteschlange wartet, wird sie aus dem Zeitplan genommen. –

Antwort

27

Vielleicht ist das Dokument hier verknüpft erklärt es ein bisschen. Es ist ein bisschen trocken, so dass nur ich geblättert es schnell

Zitiert Intro :):

Der System-Timer Auflösung bestimmt , wie häufig Windows-zwei Haupt Aktionen ausführt:

  • Aktualisieren Sie den Timer-Tick zählen, wenn ein voller Tick abgelaufen ist.
  • Überprüfen Sie, ob ein geplantes Timer-Objekt abgelaufen ist.

Ein Timer-Tick ist ein Begriff der verstrichenen Zeit, dass Windows die Tageszeit zu verfolgen, verwendet und Quanten mal einfädeln. Standardmäßig sind die Uhr Interrupt und Timer Tick die gleichen, aber Windows oder eine Anwendung kann die Uhr Interrupt-Zeitraum ändern.

Der Standard-Timer Auflösung unter Windows 7 ist 15.6 Millisekunden (ms). Einige Anwendungen reduzieren dies auf 1 ms, was die Akkulaufzeit auf mobilen Systemen um um bis zu 25 Prozent reduziert.

Ursprünglich aus: Timers, Timer Resolution, and Development of Efficient Code (docx).

+0

Danke für den Link, Arnold. Das beantwortet meine Frage nicht vollständig, aber es gibt einige Einblicke, besonders das bisschen über die Standard-Timerauflösung von 15,6 ms. –

+0

Ja, ich hatte nicht das Gefühl, alles erklärt zu haben, aber einige interessante Informationen. –

+0

Siehe auch die Antwort im Thread ["wie Timer-Auflösung von C# auf 1 ms setzen?"] (Http://stackoverflow.com/questions/15071359). –

13

Die Timer-Auflösung wird durch den Systemherzschlag gegeben. In der Regel sind dies 64 Schläge/s, was 15,625 ms entspricht. Allerdings gibt es Möglichkeiten, diese systemweite Einstellungen zu erreichen Timer Auflösungen bis hinunter zu 1 ms oder sogar bis 0,5 ms auf neueren Plattformen zu ändern:

1. Gehen für 1 ms Auflösung mit Hilfe der Multimedia-Timer-Schnittstelle:

Die Multimedia-Timer-Schnittstelle bietet eine Auflösung von bis zu 1 ms. Weitere Informationen zu timeBeginPeriod finden Sie unter About Multimedia Timers (MSDN), Obtaining and Setting Timer Resolution (MSDN) und this. Hinweis: Vergessen Sie nicht, die timeEndPeriod anzurufen, um auf die Standard-Timer-Auflösung zurückzusetzen, wenn Sie fertig sind.

Wie zu tun:

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

Hinweis: Dieses Verfahren auch auf andere Prozesse availble ist und die erhaltene Auflösung gilt weites System. Die höchste von jedem Prozess angeforderte Auflösung ist aktiv, beachten Sie die Konsequenzen.

2. Gehen zu 0,5 ms Auflösung:

Sie 0,5 ms Auflösung durch die versteckte API NtSetTimerResolution() erhalten kann. NtSetTimerResolution wird von der systemeigenen Windows NT-Bibliothek NTDLL.DLL exportiert. Siehe How to set timer resolution to 0.5ms ? auf MSDN. Die tatsächlich erreichbare Auflösung wird jedoch von der zugrunde liegenden Hardware bestimmt. Moderne Hardware unterstützt eine Auflösung von 0,5 ms. Noch mehr Details finden Sie in Inside Windows NT High Resolution Timers. Die unterstützten Auflösungen können durch einen Aufruf von NtQueryTimerResolution() erhalten werden.

Wie zu tun:

#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 

} 

Hinweis: Die Funktionalität von NtSetTImerResolution grundsätzlich auf die Funktionen zugeordnet ist timeBeginPeriodundtimeEndPeriod durch den Bool-Wert unter Verwendung von Set (siehe Inside Windows NT High Resolution Timers, um weitere Informationen über das Programm und all seine Auswirkungen). Die Multimedia-Suite begrenzt jedoch die Granularität auf Millisekunden, und NtSetTimerResolution ermöglicht das Festlegen von Werten unter Millisekunde.

+0

Basierend auf meinen Experimenten ich denke, der STATUS_TIMER_RESOLUTION_NOT_SET Fehlercode 0xC0000245 nicht 245 –

+0

@RolandPihlakas sein sollte das ist richtig, danke. Ich habe meine Antwort bearbeitet, um dies zu korrigieren. – Arno

Verwandte Themen