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.
- Quelle est la meilleure pratique pour capturer tous les détails des exceptions internes?
- Comportement non intuitif avec l’initialisation de la structure et les arguments par défaut
- .net SqlConnection n’étant pas fermé même dans un using {}
- LINQ to SQL: exception intermittente AccessViolationException encapsulée dans TargetInvocationException
- Rechercher et activer la fenêtre d’une application
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:
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.