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
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();
}
}
}
Bonne question! Je suis intéressé si quelqu'un a vraiment un aperçu de cela. Quelqu'un de l'équipe clr sur SO? –
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. –