Comment puis-je arrêter gracieusement un System.Threading.Timer?

J’ai un service Windows implémenté en C # qui a besoin de travailler de temps en temps. J’ai implémenté cela à l’aide de System.Threading.Timer avec une méthode de rappel responsable de la planification du prochain rappel. Je ne parviens pas à arrêter le chronomètre avec élégance. Voici un code simplifié que vous pouvez exécuter dans une application console qui illustre mon problème:

 const int tickInterval = 1000; // one second timer = new Timer( state => { // simulate some work that takes ten seconds Thread.Sleep( tickInterval * 10 ); // when the work is done, schedule the next callback in one second timer.Change( tickInterval, Timeout.Infinite ); }, null, tickInterval, // first callback in one second Timeout.Infinite ); // simulate the Windows Service happily running for a while before the user tells it to stop Thread.Sleep( tickInterval * 3 ); // try to gracefully dispose the timer while a callback is in progress var waitHandle = new ManualResetEvent( false ); timer.Dispose( waitHandle ); waitHandle.WaitOne(); 

Le problème est que je reçois une timer.Change ObjectDisposedException de timer.Change sur le thread de rappel pendant que waitHandle.WaitOne bloque. Qu’est-ce que je fais mal?

La documentation relative à la surcharge Dispose j’utilise indique:

La timer n’est pas supprimée tant que tous les rappels actuellement en queue ne sont pas terminés.

Edit: Il semble que cette déclaration de la documentation soit peut-être incorrecte. Quelqu’un peut vérifier?

Je sais que je pourrais contourner le problème en ajoutant une certaine signalisation entre le code de rappel et le code d’élimination, comme suggéré ci-dessous par Henk Holterman, mais je ne souhaite pas procéder ainsi sauf si c’est absolument nécessaire.

Avec ce code

  timer = new Timer( state => { // simulate some work that takes ten seconds Thread.Sleep( tickInterval * 10 ); // when the work is done, schedule the next callback in one second timer.Change( tickInterval, Timeout.Infinite ); }, null, tickInterval, // first callback in one second Timeout.Infinite ); 

il est presque certain que vous éliminerez la timer pendant son sumil.

Vous devrez sauvegarder le code après Sleep () pour détecter une timer mise au rebut. Comme il n’y a pas de propriété IsDisposed, un static bool stopping = false; rapide et sale static bool stopping = false; pourrait faire l’affaire.

Solution possible pour empêcher la méthode de rappel de travailler sur un minuteur mis au rebut:

https://stackoverflow.com/a/15902261/193178

Comme décrit dans “Programmation simultanée sous Windows”:
Créez une classe factice InvalidWaitHandle, héritant de WaitHandle:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Threading; namespace MyNameSpace { class InvalidWaitHandle : WaitHandle { } } 

Par conséquent, vous pouvez supprimer correctement un System.Threading.Timer comme ceci:

 public static void DisposeTimer() { MyTimer.Dispose(new InvalidWaitHandle()); MyTimer = null; } 

Vous n’avez pas besoin de vous débarrasser de la timer pour l’arrêter. Vous pouvez appeler Timer.Stop() ou définir Timer.Enabled sur false . L’un ou l’autre arrêtera l’exécution du chronomètre.