Perché i timer .NET sono limitati a una risoluzione di 15 ms?

Nota che ti sto chiedendo qualcosa che chiamerà una funzione di callback più spesso di una volta ogni 15 ms usando qualcosa come System.Threading.Timer . Non sto chiedendo come eseguire correttamente l’ora di un pezzo di codice usando qualcosa come System.Diagnostics.Stopwatch o anche QueryPerformanceCounter .

Inoltre, ho letto le domande correlate:

Timer di Windows accurato? System.Timers.Timer () è limitato a 15 msec

Timer ad alta risoluzione in .NET

Nessuno dei quali fornisce una risposta utile alla mia domanda.

Inoltre, l’articolo MSDN consigliato, Implementa un aggiornamento continuo, Provider di orari ad alta risoluzione per Windows , riguarda la temporizzazione delle cose piuttosto che fornire un stream continuo di tick.

Detto questo. . .

C’è un sacco di cattive informazioni là fuori sugli oggetti timer .NET. Ad esempio, System.Timers.Timer viene indicato come “un timer ad alte prestazioni ottimizzato per le applicazioni server”. E System.Threading.Timer è in qualche modo considerato un cittadino di seconda class. La saggezza convenzionale è che System.Threading.Timer è un wrapper di Timer della coda di Timer di Windows e che System.Timers.Timer è qualcosa di completamente diverso.

La realtà è molto diversa. System.Timers.Timer è solo un sottile wrapper di componenti attorno a System.Threading.Timer (basta usare Reflector o ILDASM per dare un’occhiata a System.Timers.Timer e vedrai il riferimento a System.Threading.Timer ) e ha del codice ciò fornirà la sincronizzazione automatica dei thread in modo da non doverlo fare.

System.Threading.Timer , come risulta non è un wrapper per i Timer Queue 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 il runtime .NET 4.0 implementa anche 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:

Innanzitutto, cosa causa l’implementazione del runtime della coda del timer in modo 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 usare Timer Queue 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 windows non Windows?

Il mio programma di test del timer:

 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 tickTimes = new List(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(); } } } 

Forse il documento collegato qui lo spiega un po ‘. È piuttosto secco quindi l’ho solo sfogliato velocemente 🙂

Citando l’introduzione:

La risoluzione del timer di sistema determina la frequenza con cui Windows esegue due azioni principali:

  • Aggiorna il conteggio dei tick del timer se è trascorso un tick completo.
  • Controlla se un object timer programmato è scaduto.

Un segno di spunta del timer è una nozione di tempo trascorso utilizzato da Windows per tenere traccia dell’ora del giorno e dei tempi quantici del thread. Per impostazione predefinita, l’interrupt dell’orologio e il tick del timer sono gli stessi, ma Windows o un’applicazione possono modificare il periodo di interruzione dell’orologio.

La risoluzione predefinita del timer su Windows 7 è 15.6 millisecondi (ms). Alcune applicazioni riducono questo valore a 1 ms, il che riduce il tempo di funzionamento della batteria sui sistemi mobili fino al 25 percento.

Originariamente da: timer, risoluzione timer e sviluppo di codice efficiente (docx).

La risoluzione del timer è data dal battito cardiaco del sistema. Di solito questo valore predefinito è 64 battiti / s che è 15.625 ms. Tuttavia ci sono modi per modificare queste impostazioni di sistema per ottenere risoluzioni del timer fino a 1 ms o anche a 0,5 ms su piattaforms più recenti:

1. Per la risoluzione di 1 ms tramite l’interfaccia del timer multimediale:

L’interfaccia del timer multimediale è in grado di fornire una risoluzione fino a 1 ms. Vedere Informazioni sui timer multimediali (MSDN), Ottenere e impostare la risoluzione del timer (MSDN) e questa risposta per ulteriori dettagli su timeBeginPeriod . Nota: non dimenticare di chiamare timeEndPeriod per tornare alla risoluzione predefinita del timer una volta terminato.

Come fare:

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

Nota: questa procedura è disponibile anche per altri processi e la risoluzione ottenuta si applica a livello di sistema. La risoluzione più alta richiesta da qualsiasi processo sarà triggers, considerate le conseguenze.

2. Andando alla risoluzione di 0,5 ms:

È ansible ottenere una risoluzione di 0,5 ms mediante l’API nascosta NtSetTimerResolution() . NtSetTimerResolution viene esportato dalla libreria NTDLL.DLL nativa di Windows NT. Vedi Come impostare la risoluzione del timer su 0,5 ms? su MSDN. Tuttavia, la vera risoluzione raggiungibile è determinata dall’hardware sottostante. L’hardware moderno supporta una risoluzione di 0,5 ms. Ulteriori dettagli sono disponibili in Timer ad alta risoluzione interni di Windows NT . Le risoluzioni supportate possono essere ottenute da una chiamata a NtQueryTimerResolution ().

Come fare:

 #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 } 

Nota: la funzionalità di NtSetTImerResolution è fondamentalmente mappata alle funzioni timeBeginPeriod e timeEndPeriod utilizzando il valore bool Set (vedere Dentro i timer ad alta risoluzione di Windows NT per ulteriori dettagli sullo schema e tutte le relative implicazioni). Tuttavia, la suite multimediale limita la granularità ai millisecondi e NtSetTimerResolution consente di impostare valori inferiori al millisecondo.