Domanda

Entrambi System.Timers.Timer e System.Threading.Timer si attivano a intervalli notevolmente diversi da quelli richiesti. Ad esempio:

new System.Timers.Timer(1000d / 20);

produce un timer che si attiva 16 volte al secondo, non 20.

Per essere sicuro che non ci siano effetti collaterali da gestori di eventi troppo lunghi, ho scritto questo piccolo programma di test:

int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
    int count = 0;

    // Initialize timer
    System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
    timer.Elapsed += delegate { Interlocked.Increment(ref count); };

    // Count for 10 seconds
    DateTime start = DateTime.Now;
    timer.Enabled = true;
    while (DateTime.Now < start + TimeSpan.FromSeconds(10))
        Thread.Sleep(10);
    timer.Enabled = false;

    // Calculate actual frequency
    Console.WriteLine(
        "Requested frequency: {0}\nActual frequency: {1}\n",
        frequency, count / 10d);
}

L'output è simile al seguente:

Richiesto: 5 Hz; attuale: 4,8 Hz
Richiesto: 10 Hz; attuale: 9,1 Hz
Richiesto: 15 Hz; attuale: 12,7 Hz
Richiesto: 20 Hz; attuale: 16 Hz
Richiesto: 30 Hz; attuale: 21,3 Hz
Richiesto: 50 Hz; attuale: 31,8 Hz
Richiesto: 75 Hz; attuale: 63,9 Hz
Richiesto: 100 Hz; attuale: 63,8 Hz
Richiesto: 200 Hz; attuale: 63,9 Hz
Richiesto: 500 Hz; attuale: 63,9 Hz

La frequenza effettiva si discosta fino al 36% da quella richiesta. (E evidentemente non può superare i 64 Hz.) Dato che Microsoft raccomanda questo timer per la sua "maggiore precisione" su System.Windows.Forms.Timer , questo mi confonde.

A proposito, queste non sono deviazioni casuali. Sono gli stessi valori ogni volta. E un programma di test simile per l'altra classe di timer, System.Threading.Timer , mostra esattamente gli stessi risultati.

Nel mio programma attuale, ho bisogno di raccogliere misurazioni con precisione a 50 campioni al secondo. Ciò non dovrebbe ancora richiedere un sistema in tempo reale. Ed è molto frustrante ottenere 32 campioni al secondo anziché 50.

Qualche idea?

@ Chris: Hai ragione, tutti gli intervalli sembrano multipli interi di qualcosa intorno a 1/64 di secondo. A proposito, l'aggiunta di un Thread.Sleep (...) nel gestore eventi non fa alcuna differenza. Ciò ha senso dato che System.Threading.Timer utilizza il pool di thread, quindi ogni evento viene generato su un thread gratuito.

È stato utile?

Soluzione

Bene, sto ottenendo un numero diverso fino a 100 Hz in realtà, con alcune grandi deviazioni, ma nella maggior parte dei casi più vicino al numero richiesto (eseguendo XP SP3 con i più recenti .NET SP).

System.Timer.Timer è implementato utilizzando System.Threading.Timer, quindi questo spiega perché visualizzi gli stessi risultati. Suppongo che il timer sia implementato usando un qualche tipo di algoritmo di pianificazione ecc. (È una chiamata interna, forse guardando Rotor 2.0 potrebbe far luce su di esso).

Suggerirei di implementare un tipo di timer usando un altro thread (o una combinazione di questi) che chiama Sleep e un callback. Non sono sicuro del risultato però.

Altrimenti potresti dare un'occhiata a timer multimediali (PInvoke).

Altri suggerimenti

Se si utilizza winmm.dll è possibile utilizzare più tempo della CPU, ma avere un controllo migliore.

Ecco il tuo esempio modificato per usare i timer winmm.dll

const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";

delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);

[DllImport(WINMM)]
static extern uint timeSetEvent(
      UInt32            uDelay,      
      UInt32            uResolution, 
      [MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,  
      UInt32            dwUser,      
      Int32             fuEvent      
    );

[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);

// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);

static long CPUFrequency;

static int count;

static void Main(string[] args)
{            
    QueryPerformanceFrequency(out CPUFrequency);

    int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

    foreach (int freq in frequencies)
    {
        count = 0;

        long start = GetTimestamp();

        // start timer
        uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);

        // wait 10 seconds
        while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
        {
            Thread.Sleep(1);
        }

        // end timer
        timeKillEvent(timerId);

        Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
    }

    Console.ReadLine();
}

static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
    Interlocked.Increment(ref count);
}

static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
    return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}

static public long GetTimestamp()
{
    long result;
    QueryPerformanceCounter(out result);
    return result;
}

Ed ecco l'output che ottengo:

Requested frequency: 5
Actual frequency: 5

Requested frequency: 10
Actual frequency: 10

Requested frequency: 15
Actual frequency: 15

Requested frequency: 20
Actual frequency: 19

Requested frequency: 30
Actual frequency: 30

Requested frequency: 50
Actual frequency: 50

Requested frequency: 75
Actual frequency: 76

Requested frequency: 100
Actual frequency: 100

Requested frequency: 200
Actual frequency: 200

Requested frequency: 500
Actual frequency: 500

Spero che questo aiuti.

Queste classi non sono destinate all'uso in tempo reale e sono soggette alla natura di pianificazione dinamica di un sistema operativo come Windows. Se hai bisogno di un'esecuzione in tempo reale, probabilmente vorresti guardare un po 'di hardware incorporato. Non sono sicuro al 100% ma penso che .netcpu possa essere una versione in tempo reale di un runtime .NET più piccolo su un chip.

http://www.arm.com/markets/emerging_applications/armpp /8070.html

Ovviamente, è necessario valutare l'importanza dell'accuratezza di tali intervalli che il codice ad essi associato verrà eseguito su un sistema operativo non in tempo reale. A meno che ovviamente questa non sia una domanda puramente accademica (nel qual caso, sì, è interessante!: P).

Sembra che le frequenze del tuo timer siano 63,9 Hz o multipli interi di esse.

Ciò implicherebbe una risoluzione del timer di circa 15 msec (o multipli interi di ciò, ovvero 30 msec, 45 msec, ecc.).

Questo, i timer basati su multipli interi di un 'tick', è prevedibile (in DOS ad esempio il valore 'tick' era 55 msec / 18 Hz).

Non so perché il tuo conteggio dei tick sia di 15,65 mec invece di persino 15 msec. Come esperimento, cosa succede se dormi per diversi msec nel gestore del timer: potremmo vedere 15 msec tra i tick e 0,65 msec nel gestore del timer ad ogni tick?

Windows (e quindi .NET in esecuzione su di esso) è un sistema operativo multitasking preventivo. Ogni thread può essere interrotto in qualsiasi momento da un altro thread e se il thread preempting non si comporta correttamente, non otterrai il controllo quando lo desideri o ne hai bisogno

Questo, in breve, è il motivo per cui non si può essere certi di ottenere tempi esatti e perché Windows e .NET sono piattaforme inadatte per determinati tipi di software. Se le vite sono in pericolo perché non ottieni il controllo ESATTAMENTE quando lo desideri, scegli una piattaforma diversa.

Se hai bisogno di fare un salto in un ambiente in tempo reale, ho usato RTX in passato quando era necessario il campionamento deterministico (da un dispositivo seriale personalizzato) e ho avuto molta fortuna con esso.

http://www.pharlap.com/rtx.htm

Ecco una bella implementazione di un timer che usa il timer multimediale http: // www. softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

Ci sono molte ragioni per cui il timer si spegne. Esempio con l'hardware, lo stesso thread è occupato con un'altra elaborazione e così via ...

Se vuoi un tempo più preciso, usa la classe Stopwatch nello spazio dei nomi System.Diagnostics.

Parte del problema è che i timer hanno due ritardi da tenere in considerazione. Questo può variare in base alla modalità di implementazione del timer nel sistema operativo.

  1. Il tempo che si richiede di attendere
  2. Il tempo che intercorre tra il momento in cui si verifica il n. 1 e il processo fa il suo turno nella coda di pianificazione.

Il timer ha un buon controllo del n. 1 ma quasi nessun controllo sul n. 2. Può segnalare al sistema operativo che vorrebbe eseguire di nuovo, ma il sistema operativo è libero di svegliarlo quando ne ha voglia.

Sulla base dei tuoi commenti, non dovresti usare affatto un timer. Dovresti usare un loop con un cronometro per controllare l'intervallo e uno spinlock in modo da non perdere il quantum.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top