2010-09-19 18 views
52

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

High resolution timer in .NET

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

Buona domanda! Sono interessato se qualcuno in realtà ha un'idea di questo. Qualcuno del team clr su SO? –

+0

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. –

risposta

27

Forse il documento collegato qui spiega un po '. È un po 'asciutto in modo ho passato in rassegna solo :) rapidamente

Citando l'intro:

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

  • aggiornare la graduazione del timer conta se è trascorso un segno di spunta completo.
  • Verificare se un oggetto timer programmato è scaduto.

un timer è una nozione del tempo trascorso che Windows utilizza per tenere traccia del tempo del giorno e filo volte quantistica. Per impostazione predefinita, l'interruzione dell'orologio e il tick del timer sono uguali, ma Windows o un'applicazione può modificare il periodo di interruzione dell'orologio .

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

Originario di: Timers, Timer Resolution, and Development of Efficient Code (docx).

+0

Grazie per il link, Arnold. Questo non risponde completamente alla mia domanda, ma fornisce alcune informazioni, in particolare il bit sulla risoluzione del timer predefinita di 15.6 ms. –

+0

Sì, non ho avuto la sensazione che spiegasse tutto ma avevo alcune informazioni interessanti. –

+0

Vedere anche la risposta nel thread ["come impostare la risoluzione del timer da C# a 1 ms?"] (Http://stackoverflow.com/questions/15071359). –

13

La risoluzione del timer è dato 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 a livello di sistema per ottenere risoluzioni timer fino a 1 ms o addirittura a 0,5 ms su piattaforme più recenti:

1. Andando per 1 ms risoluzione tramite l'interfaccia multimediale temporizzatore:

L'interfaccia del timer multimediale è in grado di fornire una risoluzione fino a 1 ms. Vedere About Multimedia Timers (MSDN), Obtaining and Setting Timer Resolution (MSDN) e this risposta per ulteriori dettagli su timeBeginPeriod. Nota: non dimenticare di chiamare lo 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 è availble ad altri processi come bene e la risoluzione ottenuta si applica a livello di sistema. La risoluzione più alta richiesta da qualsiasi processo sarà attiva, considerate le conseguenze.

2. Andando a risoluzione 0,5 ms:

È può ottenere 0,5 ms risoluzione mediante l'API nascosta NtSetTimerResolution(). NtSetTimerResolution viene esportato dalla libreria NTDLL.DLL nativa di Windows NT. Vedere How to set timer resolution to 0.5ms ? su MSDN. Tuttavia, la vera risoluzione raggiungibile è determinata dall'hardware sottostante. L'hardware moderno supporta una risoluzione di 0,5 ms. Ancora più dettagli sono disponibili in Inside Windows NT High Resolution Timers. 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 associato alle funzioni timeBeginPeriodetimeEndPeriod utilizzando il valore bool Set (vedi Inside Windows NT High Resolution Timers per maggiori dettagli circa il sistema e tutte le sue implicazioni). Tuttavia, la suite multimediale limita la granularità ai millisecondi e NtSetTimerResolution consente di impostare valori inferiori al millisecondo.

+0

Sulla base dei miei esperimenti, penso che il codice di errore STATUS_TIMER_RESOLUTION_NOT_SET debba essere 0xC0000245 non 245 –

+0

@RolandPihlakas Questo è corretto, grazie. Ho modificato la mia risposta per correggerla. – Arno

Problemi correlati