2009-11-13 5 views
3

J'ai écrit une classe Timer afin de l'utiliser dans un service Windows qui interroge un autre système. Je l'ai fait parce que j'avais deux problèmes que le System.Timers.Timer n'adresse pas. Le Elapsed EventHanler s'exécute en arrière-plan et son exécution s'interrompt si le thread principal se termine. Je voulais que la fonction System.Timers.Timer.Stop bloque le thread principal jusqu'à la fin de l'exécution du Elapsed EventHanler. Minuteur pour un service de fenêtres d'interrogation

  • System.Timers.Timer ne traite pas de la réentrance d'événement. Je veux que l'Intervalle soit entre deux EventHanler écoulés de sorte que le Timer ne puisse jamais appeler le EventHanler Elapsed si l'appel précédent (+ intervalle) n'était pas encore fini. Lors de l'écriture de la classe, j'ai découvert que je devais résoudre certains problèmes liés à la thrading et que je ne connaissais pas trop ceux que je veux savoir si la classe Timer suivante est Thread-Safe?

    public class Timer 
    { 
        System.Timers.Timer timer = new System.Timers.Timer() { AutoReset = false }; 
        ManualResetEvent busy = new ManualResetEvent(true); 
    
        public double Interval 
        { 
         get { return timer.Interval; } 
         set { timer.Interval = value; } 
        } 
    
        public Timer() 
        { 
         timer.Elapsed += new ElapsedEventHandler(TimerElapsed); 
        } 
    
        void TimerElapsed(object sender, ElapsedEventArgs e) 
        { 
         try 
         { 
          busy.Reset(); 
          OnElapsed(e); 
          timer.Start(); 
         } 
         finally 
         { 
          busy.Set(); 
         } 
        } 
    
        public event EventHandler Elapsed; 
    
        protected void OnElapsed(EventArgs e) 
        { 
         if (Elapsed != null) 
         { 
          Elapsed(this, e); 
         } 
        } 
    
        public virtual void Start() 
        { 
         busy.WaitOne(); 
         timer.Start(); 
        } 
    
        public virtual void Stop() 
        { 
         busy.WaitOne(); 
         timer.Stop(); 
        } 
    } 
    
  • Répondre

    5

    tout d'abord, vous pouvez utiliser System.Threading.Timer au lieu de cette minuterie selon mon expérience qui est une meilleure minuterie performante (juste un advise sur l'expérience personnelle). Deuxièmement, dans de tels cas, vous devriez donner un drapeau qui est défini une fois que la minuterie précédente a terminé la tâche (cet indicateur - un champ statique auquel tous les threads ont accès). Dans ce cas, assurez-vous qu'en cas d'erreur, le drapeau est réinitialisé par vous de sorte que l'autre temporisateur n'attende pas indéfiniment dans le cas où la tâche de temporisation n'est pas en mesure de définir le drapeau pour d'autres temporisateurs en raison du une erreur s'est produite à l'intérieur de la tâche (une sorte de bloc final doit être ajoutée pour s'assurer que l'erreur est gérée et que l'indicateur est toujours réinitialisé).

    Une fois que ce drapeau est réinitialisé, le thread suivant travaille dessus donc cette vérification ferait en sorte que tous les threads commencent la tâche sur le même par un.

    Exemple de code que j'ai écrit pour une situation comme celle-ci (le code des méthodes a été supprimé, cela vous donnera les détails de conception).

    namespace SMSPicker 
    { 
    public partial class SMSPicker : ServiceBase{ 
        SendSMS smsClass; 
        AutoResetEvent autoEvent; 
        TimerCallback timerCallBack; 
        Timer timerThread; 
        public SMSPicker() 
        { 
         InitializeComponent(); 
        } 
    
        protected override void OnStart(string[] args) 
        { 
         // TODO: Add code here to start your service. 
         smsClass = new SendSMS(); 
         autoEvent = new AutoResetEvent(false); 
         long timePeriod = string.IsNullOrEmpty(ConfigurationSettings.AppSettings["timerDuration"]) ? 10000 : Convert.ToInt64(ConfigurationSettings.AppSettings["timerDuration"]); 
         timerCallBack = new TimerCallback(sendSMS); 
         timerThread = new Timer(timerCallBack, autoEvent, 0, timePeriod); 
        } 
    
    
        private void sendSMS(object stateInfo) 
        { 
         AutoResetEvent autoResetEvent = (AutoResetEvent)stateInfo; 
         smsClass.startSendingMessage(); 
         autoResetEvent.Set(); 
        } 
    
        protected override void OnStop() 
        { 
         // TODO: Add code here to perform any tear-down necessary to stop your service. 
         smsClass.stopSendingMessage(); 
         timerThread.Dispose();    
    
        } 
    } 
    } 
    
    
    
    
    
    
    
    namespace SMSPicker 
    { 
    class SendSMS 
    { 
        //This variable has been done in order to ensure that other thread does not work till this thread ends 
        bool taskDone = true; 
        public SendSMS() 
        { 
    
        } 
    
        //this method will start sending the messages by hitting the database 
        public void startSendingMessage() 
        { 
    
         if (!taskDone) 
         { 
          writeToLog("A Thread was already working on the same Priority."); 
          return; 
         } 
    
         try 
         { 
         } 
         catch (Exception ex) 
         { 
          writeToLog(ex.Message); 
         } 
         finally 
         { 
          taskDone = stopSendingMessage(); 
    
          //this will ensure that till the database update is not fine till then, it will not leave trying to update the DB 
          while (!taskDone)//infinite looop will fire to ensure that the database is updated in every case 
          { 
           taskDone = stopSendingMessage(); 
          } 
         } 
    
        } 
    
    
    public bool stopSendingMessage() 
        { 
         bool smsFlagUpdated = true; 
         try 
         { 
    
         } 
         catch (Exception ex) 
         { 
          writeToLog(ex.Message); 
         } 
         return smsFlagUpdated; 
        } 
    
    } 
    } 
    
    0

    J'ai eu un problème similaire: la chose la plus simple à faire pourrait être d'utiliser System.Threading.Timer avec period = 0;

    Dans ce scénario, la méthode de rappel est appelée une seule fois. Ensuite, la réinitialisation du temporisateur (appel de sa méthode Change()) le rallume; à la fin de votre méthode de répétition.

    Ce scénario est expliqué ici: http://kristofverbiest.blogspot.com/2008/08/timers-executing-task-at-regular.html

    +1

    cette solution ne résout pas le problème décrit en 1. – Martin

    0

    Une autre façon de le faire serait d'attendre un événement plutôt que d'utiliser une minuterie.Public class PollingService { private Thread _workerThread; private AutoResetEvent _finished; privé const int _timeout = 60 * 1000;

    public void StartPolling() 
    { 
        _workerThread = new Thread(Poll); 
        _finished = new AutoResetEvent(false); 
        _workerThread.Start(); 
    } 
    
    private void Poll() 
    { 
        while (!_finished.WaitOne(_timeout)) 
        { 
         //do the task 
        } 
    } 
    
    public void StopPolling() 
    { 
        _finished.Set(); 
        _workerThread.Join(); 
    } 
    

    }

    Dans votre service

    public partial class Service1 : ServiceBase 
    { 
        private readonly PollingService _pollingService = new PollingService(); 
        public Service1() 
        { 
         InitializeComponent(); 
        } 
    
        protected override void OnStart(string[] args) 
        { 
         _pollingService.StartPolling(); 
        } 
    
        protected override void OnStop() 
        { 
         _pollingService.StopPolling(); 
        } 
    
    } 
    
    0

    Vous pouvez utiliser une minuterie ou un thread dédié/tâche qui dort ou autre. Personnellement, je trouve que le thread/tâche dédié est plus facile à utiliser qu'une minuterie pour ce genre de choses car il est plus facile de contrôler l'intervalle d'interrogation. En outre, vous devez absolument utiliser le mécanisme d'annulation coopératif fourni avec le TPL. Il ne faut pas jeter des exceptions. Il le fait seulement si vous appelez ThrowIfCancellationRequested. Vous pouvez utiliser IsCancellationRequested au lieu de simplement vérifier l'état du jeton d'annulation.

    Voici un modèle très générique que vous pouvez utiliser pour commencer.

    public class YourService : ServiceBase 
    { 
        private CancellationTokenSource cts = new CancellationTokenSource(); 
        private Task mainTask = null; 
    
        protected override void OnStart(string[] args) 
        { 
        mainTask = new Task(Poll, cts.Token, TaskCreationOptions.LongRunning); 
        mainTask.Start(); 
        } 
    
        protected override void OnStop() 
        { 
        cts.Cancel(); 
        mainTask.Wait(); 
        } 
    
        private void Poll() 
        { 
        CancellationToken cancellation = cts.Token; 
        TimeSpan interval = TimeSpan.Zero; 
        while (!cancellation.WaitHandle.WaitOne(interval)) 
        { 
         try 
         { 
         // Put your code to poll here. 
         // Occasionally check the cancellation state. 
         if (cancellation.IsCancellationRequested) 
         { 
          break; 
         } 
         interval = WaitAfterSuccessInterval; 
         } 
         catch (Exception caught) 
         { 
         // Log the exception. 
         interval = WaitAfterErrorInterval; 
         } 
        } 
        } 
    }