Nota che ti sto chiedendo qualcosa che richiamerà una funzione di callback più spesso di una volta ogni 15 ms utilizzando qualcosa come System.Threading.Timer
. Non sto chiedendo come temporizzare accuratamente un pezzo di codice usando qualcosa come System.Diagnostics.Stopwatch
o anche QueryPerformanceCounter
.Perché i timer .NET sono limitati a una risoluzione di 15 ms?
Inoltre, ho letto le questioni correlate:
Accurate Windows timer? System.Timers.Timer() is limited to 15 msec
Nessuno dei quali fornisce una soluzione utile alla mia domanda.
Inoltre, l'articolo MSDN consigliato, Implement a Continuously Updating, High-Resolution Time Provider for Windows, riguarda la temporizzazione delle cose anziché fornire un flusso continuo di zecche.
Con ciò detto. . .
C'è un sacco di informazioni errate là fuori sugli oggetti del timer .NET. Ad esempio, System.Timers.Timer
viene addebitato come "un timer ad alte prestazioni ottimizzato per le applicazioni server". E System.Threading.Timer
è in qualche modo considerato un cittadino di seconda classe. La saggezza convenzionale è che System.Threading.Timer
è un wrapper su Windows Timer Queue Timers e che è qualcosa di completamente diverso.
La realtà è molto diversa. System.Timers.Timer
è solo un sottile involucro del componente intorno a System.Threading.Timer
(basta usare Reflector o ILDASM per guardare all'interno di System.Timers.Timer
e vedrai il riferimento a System.Threading.Timer
) e ha del codice che fornirà la sincronizzazione automatica dei thread in modo da non doverlo fare.
System.Threading.Timer
, come risulta non è un wrapper per i Timer della coda timer. Almeno non nel runtime 2.0, che è stato utilizzato da .NET 2.0 a .NET 3.5. Alcuni minuti con la CLI di origine condivisa mostrano che il runtime implementa la propria coda timer simile ai Timer della coda timer, ma in realtà non chiama mai le funzioni Win32.
Sembra che anche il runtime .NET 4.0 implementa la propria coda di timer. Il mio programma di test (vedi sotto) fornisce risultati simili sotto .NET 4.0 come sotto .NET 3.5. Ho creato il mio wrapper gestito per i Timer Queue Timer e ho dimostrato che posso ottenere una risoluzione di 1 ms (con una precisione abbastanza buona), quindi ritengo improbabile che io stia leggendo la sorgente CLI errata.
Ho due domande:
In primo luogo, ciò che provoca l'implementazione del tempo di esecuzione della coda timer per essere così lento? Non riesco a ottenere una risoluzione migliore di 15 ms e la precisione sembra essere nell'intervallo da -1 a +30 ms. Cioè, se chiedo 24 ms, otterrò tick da qualunque posto tra 23 e 54 ms. Suppongo che potrei passare un po 'più di tempo con la sorgente CLI per rintracciare la risposta, ma ho pensato che qualcuno potrebbe saperlo.
In secondo luogo, e mi rendo conto che è più difficile rispondere, perché non utilizzare i timer della coda timer? Mi rendo conto che .NET 1.x doveva essere eseguito su Win9x, che non aveva quelle API, ma esistono da Windows 2000, che se ricordo correttamente era il requisito minimo per .NET 2.0. È perché la CLI doveva funzionare su finestre non Windows?
miei timer programma di 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();
}
}
}
Buona domanda! Sono interessato se qualcuno in realtà ha un'idea di questo. Qualcuno del team clr su SO? –
Scommetto che win9x aveva anche tick nel kernel. dopotutto era un ambiente di lavoro preventivo. tutto il merdoso ma ancora vero multitasking. quindi non puoi fare la prelazione senza interruzioni, e tendono a sparare da un normale timer hardware, specialmente negli anni 90, basato sull'HTC (64 Hz). Come si fa la GUI senza una coda di eventi? gli eventi del timer vengono inviati alla coda eventi dal kernel, non c'è modo di aggirarlo perché mentre la tua app non sta facendo nulla in attesa della coda, è de-programmata. –