Warum sind .NET-Timer auf eine Auflösung von 15 ms begrenzt?

Bitte beachten Sie, dass ich nach etwas frage, das alle 15 ms öfter als einmal eine Rückruffunktion aufruft, indem ich so etwas wie @ verwendSystem.Threading.Timer. Ich frage nicht, wie man einen Code mit so etwas wie @ genau zeitlich festlegSystem.Diagnostics.Stopwatch oder auchQueryPerformanceCounter.

Auch habe ich die verwandten Fragen gelesen:

Genaue Windows-Timer? System.Timers.Timer () ist auf 15 ms begrenzt

Hochauflösender Timer in .NET

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

Darüber hinaus enthält der empfohlene MSDN-ArtikelImplementieren Sie einen ständig aktualisierten, hochauflösenden Zeitprovider für Windowsei @ geht es eher um das Timing, als um die Bereitstellung eines kontinuierlichen Stroms von Ticks.

Nachdem das gesagt worden ist. . .

Es gibt viele schlechte Informationen zu den .NET-Timer-Objekten. Zum Beispiel,System.Timers.Timer wird als "für Serveranwendungen optimierter Hochleistungstimer" bezeichnet. UndSystem.Threading.Timer wird irgendwie als Bürger zweiter Klasse angesehen. Die übliche Weisheit ist, dassSystem.Threading.Timer ist ein Wrapper um Windows Timer Queue Timer und dasSystem.Timers.Timer ist etwas ganz anderes.

Die Realität sieht anders aus.System.Timers.Timer ist nur ein dünner Komponentenwickler umSystem.Threading.Timer (Verwenden Sie einfach Reflector oder ILDASM, um einen Blick in @ zu werfeSystem.Timers.Timer und du siehst den Verweis aufSystem.Threading.Timer) und verfügt über einen Code, der die automatische Thread-Synchronisierung ermöglicht, sodass Sie dies nicht tun müssen.

System.Threading.Timer, wie sich herausstelltist nich ein Wrapper für die Timer Queue 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 zeigen, dass die Laufzeitumgebung eine eigene Timer-Warteschlange implementiert, die den Timer-Warteschlangen-Timern ähnelt, jedoch niemals die Win32-Funktionen 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-Warteschlangentimer erstellt und bewiesen, dass ich eine Auflösung von 1 ms (mit ziemlich guter Genauigkeit) erreichen kann. Daher halte ich es für unwahrscheinlich, dass ich die CLI-Quelle falsch lese.

Ich habe zwei Fragen

Zunächst, warum ist die Implementierung der Timer-Warteschlange zur Laufzeit so langsam? Ich kann keine bessere Auflösung als 15 ms erreichen und die Genauigkeit scheint im Bereich von -1 bis +30 ms zu liegen. Das heißt, wenn ich nach 24 ms frage, bekomme ich Ticks zwischen 23 und 54 ms. Ich nehme an, ich könnte etwas mehr Zeit mit der CLI-Quelle verbringen, um die Antwort zu finden, aber ich dachte, jemand hier könnte es wissen.

Second, und mir ist klar, dass dies schwieriger zu beantworten ist. Warum nicht die Timer-Warteschlangen-Timer verwenden? Mir ist klar, dass .NET 1.x auf Win9x laufen musste, das diese APIs nicht hatte, aber es gibt sie seit Windows 2000. Wenn ich mich recht erinnere, war das die Mindestanforderung für .NET 2.0. Liegt es daran, dass die CLI auf Nicht-Windows-Boxen ausgeführt werden musste?

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

Antworten auf die Frage(6)

Ihre Antwort auf die Frage