Question

System.Timers.Timer et System.Threading.Timer se déclenchent à des intervalles très différents de ceux demandés. Par exemple:

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

génère une minuterie qui se déclenche 16 fois par seconde et non pas 20.

Pour être sûr qu'il n'y ait pas d'effets secondaires de gestionnaires d'événements trop longs, j'ai écrit ce petit programme de 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);
}

La sortie ressemble à ceci:

demandé: 5 Hz; actuel: 4,8 Hz
Demandé: 10 Hz; actuel: 9,1 Hz
Demandé: 15 Hz; actuel: 12,7 Hz
Demandé: 20 Hz; actuel: 16 Hz
Demandé: 30 Hz; actuel: 21,3 Hz
Demandé: 50 Hz; actuel: 31,8 Hz
Demandé: 75 Hz; actuel: 63,9 Hz
Demandé: 100 Hz; actuel: 63,8 Hz
Demandé: 200 Hz; actuel: 63,9 Hz
Demandé: 500 Hz; réelle: 63,9 Hz

La fréquence réelle s'écarte de 36% maximum de la fréquence demandée. (Et évidemment, ne peut pas dépasser 64 Hz.) Etant donné que Microsoft recommande cette minuterie pour sa "plus grande précision" sur System.Windows.Forms.Timer , cela me laisse perplexe.

Btw, ce ne sont pas des déviations aléatoires. Ce sont les mêmes valeurs à chaque fois. Et un programme de test similaire pour l'autre classe de minuteur, System.Threading.Timer , affiche exactement les mêmes résultats.

Dans mon programme actuel, je dois collecter des mesures exactement à 50 échantillons par seconde. Cela ne devrait pas encore nécessiter un système en temps réel. Et il est très frustrant d’obtenir 32 échantillons par seconde au lieu de 50.

Des idées?

@Chris: Vous avez raison, les intervalles semblent tous être des multiples entiers d'environ 1 / 64ème de seconde. Au fait, l'ajout d'un Thread.Sleep (...) dans le gestionnaire d'événements ne fait aucune différence. Cela a du sens étant donné que System.Threading.Timer utilise le pool de threads, chaque événement est donc déclenché sur un thread libre.

Était-ce utile?

La solution

Eh bien, je reçois un numéro différent jusqu’à 100 Hz, avec de gros écarts, mais dans la plupart des cas plus proche du numéro demandé (avec XP SP3 avec les SP les plus récents .NET).

System.Timer.Timer est implémenté à l'aide de System.Threading.Timer. Cela explique pourquoi vous obtenez les mêmes résultats. Je suppose que le minuteur est implémenté à l'aide d'un algorithme de planification, etc. (c'est un appel interne; peut-être que regarder Rotor 2.0 pourrait nous éclairer un peu).

Je suggérerais de mettre en place une sorte de minuteur en utilisant un autre thread (ou une combinaison de ceux-ci) appelant Sleep et un rappel. Pas sûr du résultat cependant.

Sinon, vous pouvez consulter minuteries multimédias (PInvoke).

Autres conseils

Si vous utilisez winmm.dll, vous pouvez utiliser plus de temps processeur, tout en ayant un meilleur contrôle.

Voici votre exemple modifié pour utiliser les minuteries 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;
}

Et voici le résultat obtenu:

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

J'espère que cela vous aidera.

Ces classes ne sont pas destinées à une utilisation en temps réel et sont soumises à la nature de planification dynamique d'un système d'exploitation tel que Windows. Si vous avez besoin d'une exécution en temps réel, vous voudrez probablement utiliser du matériel intégré. Je ne suis pas sûr à 100% mais je pense que .netcpu est peut-être une version en temps réel d'un runtime .NET plus petit sur une puce.

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

Bien sûr, vous devez évaluer l’importance de la précision de ces intervalles pour que le code qui leur est associé soit exécuté sur un système d’exploitation en temps non réel. À moins bien sûr que ce soit une question purement académique (dans ce cas, oui, c’est intéressant!: P).

Il semble que vos fréquences de minuterie réelles soient de 63,9 Hz ou de plusieurs de leurs entiers.

Cela impliquerait une résolution de la minuterie d’environ 15 ms (ou des multiples entiers de celle-ci, c’est-à-dire 30 ms, 45 ms, etc.).

Ceci, des minuteries basées sur des multiples entiers d'un "tick" doivent être attendus (sous DOS par exemple, la valeur du "tick" était de 55 ms / 18 Hz).

Je ne sais pas pourquoi votre compte de ticks est de 15,65 mec au lieu de 15 ms. A titre expérimental, que se passe-t-il si vous dormez plusieurs ms dans le gestionnaire de minuteur: pouvons-nous observer 15 ms entre les ticks et 0,65 ms dans votre gestionnaire de minuteur à chaque tick?

Windows (et par conséquent, .NET s'exécutant par-dessus) est un système d'exploitation multitâche préemptif. Tout thread donné peut être arrêté à tout moment par un autre thread. Si le thread préempteur ne se comporte pas correctement, vous ne récupérerez pas le contrôle quand vous le voudrez ou le aurez besoin de .

En résumé, c’est la raison pour laquelle on ne peut pas garantir la précision du minutage ni la raison pour laquelle Windows et .NET sont des plates-formes inappropriées pour certains types de logiciels. Si des vies sont en danger parce que vous ne contrôlez PAS EXACTEMENT lorsque vous le souhaitez, choisissez une plate-forme différente.

Si vous avez besoin de passer à un environnement en temps réel, j’avais utilisé RTX dans le passé, lorsque l’échantillonnage déterministe était nécessaire (à partir d’un périphérique série personnalisé) et que j’avais eu beaucoup de chance.

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

Voici une belle implémentation d'une minuterie utilisant une minuterie multimédia http: // www. softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

Il y a beaucoup de raisons pour lesquelles la fréquence du minuteur est désactivée. Exemple avec le matériel, le même thread est occupé par un autre traitement, et ainsi de suite ...

Si vous souhaitez une heure plus précise, utilisez la classe Stopwatch dans l'espace de noms System.Diagnostics.

Une partie du problème tient au fait que les minuteurs ont deux délais à prendre en compte. Cela peut varier en fonction de la manière dont le minuteur est implémenté dans le système d'exploitation.

  1. l'heure à laquelle vous demandez d'attendre
  2. Le temps écoulé entre le moment où # 1 s'est produit et le processus est à son tour dans la file d'attente de planification.

La minuterie a un bon contrôle sur # 1 mais presque aucun contrôle sur # 2. Il peut signaler au système d’exploitation qu’il aimerait fonctionner à nouveau, mais le système d’exploitation est libre de le réactiver quand il le souhaite.

Selon vos commentaires, vous ne devriez pas utiliser de minuterie. Vous devriez utiliser une boucle avec un chronomètre pour vérifier l’intervalle et un spinlock afin de ne pas perdre le quantum.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top