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
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();
}
}
}
Gute Frage! Ich bin interessiert, wenn jemand tatsächlich Einblick in das hat. Jeder von der Clr-Team auf SO? –
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. –