2009-11-09 7 views
16

Ho un System.Threading.Timer che chiama il relativo gestore di eventi (callback) ogni 10 ms. Il metodo stesso è non rientrante e talvolta può richiedere molto più lungo di 10 ms. Pertanto, voglio interrompere il timer durante l'esecuzione del metodo.Arresto del timer nel suo metodo di richiamata

Codice:

private Timer _creatorTimer; 

// BackgroundWorker's work 
private void CreatorWork(object sender, DoWorkEventArgs e) { 
     _creatorTimer = new Timer(CreatorLoop, null, 0, 10); 

     // some other code that worker is doing while the timer is active 
     // ... 
     // ... 
} 

private void CreatorLoop(object state) { 
     // Stop timer (prevent reentering) 
     _creatorTimer.Change(Timeout.Infinite, 0); 

     /* 
      ... Work here 
     */ 

     // Reenable timer 
     _creatorTimer.Change(10, 0); 
} 

MSDN afferma che il metodo di callback viene chiamato (ogni volta che il timer incendi) in thread separato dal pool di thread. Ciò significa che se interrompo il timer, la prima cosa nel metodo non impedisce necessariamente al timer di sparare ed eseguire un'altra istanza del metodo prima che il primo avesse la possibilità di fermare il timer.

Dovrebbe forse essere bloccato il timer (o anche il metodo non rientrante)? Qual è il modo giusto per evitare che il timer si attivi durante l'esecuzione del suo metodo di callback (e non rientranti)?

+0

questa domanda può aiutarti http://stackoverflow.com/questions/1116249/manualresetevent-vs-thread-sleep – Kane

risposta

45

È possibile che il timer continui a attivare il metodo di richiamata, ma avvolgere il codice non rientrante in Monitor.TryEnter/Exit. In questo caso non è necessario arrestare/riavviare il timer; le chiamate sovrapposte non acquisiranno il blocco e torneranno immediatamente.

private void CreatorLoop(object state) 
{ 
    if (Monitor.TryEnter(lockObject)) 
    { 
    try 
    { 
     // Work here 
    } 
    finally 
    { 
     Monitor.Exit(lockObject); 
    } 
    } 
} 
+0

+1 Non ho mai veramente pensato a un uso per TryEnter; è molto interessante. – Schmuli

+0

Sembra che stia facendo il trucco. Due o più thread possono entrare nel metodo, ma solo uno funzionerà effettivamente. Ho anche interrotto il timer dopo Monitor.TryEnter() in modo che non si attivi affatto durante l'esecuzione (non è necessario che si attivi), nel caso in cui il tempo di esecuzione sia molto maggiore del periodo del timer. Il timer viene riavviato al termine del lavoro nel metodo. –

+0

+1 Bella soluzione! – ParmesanCodice

0

Ho avuto una situazione simile con un System.Timers.Timer, in cui l'evento trascorso viene eseguito da un threadpool e deve essere rientranti.

Ho usato questo metodo per aggirare il problema:

private void tmr_Elapsed(object sender, EventArgs e) 
{ 
    tmr.Enabled = false; 
    // Do Stuff 
    tmr.Enabled = true; 
} 

A seconda di quello che stai facendo si può prendere in considerazione uno System.Timers.Timer, ecco un bel riassunto da MSDN

          System.Windows.Forms System.Timers   System.Threading 
Timer event runs on what thread?   UI thread    UI or worker thread Worker thread 
Instances are thread safe?    No      Yes     No 
Familiar/intuitive object model?   Yes      Yes     No 
Requires Windows Forms?     Yes      No     No 
Metronome-quality beat?     No      Yes*     Yes* 
Timer event supports state object?  No      No     Yes 
Initial timer event can be scheduled? No      No     Yes 
Class supports inheritance?    Yes      Yes     No 

* Depending on the availability of system resources (for example, worker threads)    
+0

credo che questa in realtà non aggira il problema. Beh, potrebbe verificarsi nel 99,9% dei casi, ma se il sistema non fornisce il tempo del processore al gestore eventi prima dell'avvio dell'evento Elapsed del timer successivo, due thread differenti possono eseguire il metodo in parallelo. –

+0

Buon punto! Puoi sempre usarlo in combinazione con una soluzione di blocco come jsw's – ParmesanCodice

6

Un paio di possibili soluzioni:

  • hanno il vero lavoro fatto in un altro delegato filo che è in attesa di un evento. La richiamata del timer segnala semplicemente l'evento. Il thread di lavoro non può essere reinserito, poiché è un singolo thread che funziona solo quando viene segnalato l'evento. Il timer è rientrante, poiché tutto ciò che fa è segnalare l'evento (sembra un po 'rotatorio e dispendioso, ma funzionerà)
  • avere il timer creato con solo un timeout di inizio e nessun timeout periodico in modo che si spari solo una volta . Il callback del timer eliminerà quell'oggetto timer e ne creerà uno nuovo quando avrà completato il suo lavoro, che verrà attivato anche una sola volta.

Si può essere in grado di gestire l'opzione # 2 senza smaltimento/creazione di un nuovo oggetto utilizzando il metodo dell'oggetto temporizzatore originale Change(), ma non sono sicuro di quello che il comportamento è esattamente di chiamare Change() con un nuovo avvia il timeout dopo che è scaduto il primo timeout. Questo varrebbe la pena di fare un test o due.

Edit:


Ho fatto il test - manipolare il timer come riavviabile one-shot sembra funzionare perfettamente, ed è molto più semplice rispetto agli altri metodi.Ecco alcuni esempi di codice sulla base di vostro come punto di partenza (alcuni dettagli potrebbero essere cambiati per farlo compilare sulla mia macchina):

private Timer _creatorTimer; 

// BackgroundWorker's work 
private void CreatorWork(object sender, EventArgs e) { 
    // note: there's only a start timeout, and no repeat timeout 
    // so this will fire only once 
    _creatorTimer = new Timer(CreatorLoop, null, 1000, Timeout.Infinite); 

    // some other code that worker is doing while the timer is active 
    // ... 
    // ... 
} 

private void CreatorLoop(object state) { 
    Console.WriteLine("In CreatorLoop..."); 
    /* 
     ... Work here 
    */ 
    Thread.Sleep(3000); 

    // Reenable timer 
    Console.WriteLine("Exiting..."); 

    // now we reset the timer's start time, so it'll fire again 
    // there's no chance of reentrancy, except for actually 
    // exiting the method (and there's no danger even if that 
    // happens because it's safe at this point). 
    _creatorTimer.Change(1000, Timeout.Infinite); 
} 
+0

Sembra funzionare, sì, ma il codice diventa meno pulito quando ci sono molti risultati del metodo (molti rami e eccezioni if-else). Ma sembra essere una buona soluzione quando si tratta di prestazioni perché non vengono utilizzati meccanismi di sincronizzazione. Oltre a questo, questo NON FUNZIONERÀ se ci sono altri metodi/thread/timer che possono provare ad entrare in questo metodo. Quindi, naturalmente, stiamo rientrando nel metodo non rientranti, che è dove i monitor funzionano meglio. Grazie per la soluzione e il test, comunque. È una bella idea –

+0

La complessità del codice non è più un problema che se si utilizza un mutex - basta avvolgere il codice in un 'try' /' finally' o semplicemente chiamare un'altra routine che ha la complessità e lasciare la routine di callback del timer a semplice 'call then reset the timer'. Se la callback sarà utilizzata da più timer, allora questa tecnica non funzionerà e avrai bisogno di un vero oggetto di sincronizzazione. Trovo che sia piuttosto raro che un callback del timer sia utilizzato da più timer, in particolare quando la callback è molto complessa (in genere significa che la richiamata del timer è progettata per uno scopo ben preciso). –

+0

È possibile ottenere lo stesso comportamento utilizzando System.Timers.Timer che ritengo sia molto più semplice. Vedi http://stackoverflow.com/questions/7055820/non-reentrant-timers/ –

0

lo faccio con Interlocked che fornisce operazioni atomiche, e CompareExchange assicura che solo un thread alla volta entra nella sezione critica:

private int syncPoint = 0; 

private void Loop() 
    { 
     int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); 
     //ensures that only one timer set the syncPoint to 1 from 0 
     if (sync == 0) 
     { 
      try 
      { 
       ... 
      } 
      catch (Exception pE) 
      { 
       ... 
      } 
      syncPoint = 0; 
     } 

    } 
0
//using Timer with callback on System.Threading namespace 
    // Timer(TimerCallback callback, object state, int dueTime, int period); 
    //  TimerCallback: delegate to callback on timer lapse 
    //  state: an object containig information for the callback 
    //  dueTime: time delay before callback is invoked; in milliseconds; 0 immediate 
    //  period: interval between invocation of callback; System.Threading.Timeout.Infinity to disable 
    // EXCEPTIONS: 
    //  ArgumentOutOfRangeException: negative duration or period 
    //  ArgumentNullException: callback parameter is null 

    public class Program 
    { 
     public void Main() 
     { 
      var te = new TimerExample(1000, 2000, 2); 
     } 
    } 

    public class TimerExample 
    { 
     public TimerExample(int delayTime, int intervalTime, int treshold) 
     { 
      this.DelayTime = delayTime; 
      this.IntervalTime = intervalTime; 
      this.Treshold = treshold; 
      this.Timer = new Timer(this.TimerCallbackWorker, new StateInfo(), delayTime, intervalTime); 
     } 

     public int DelayTime 
     { 
      get; 
      set; 
     } 

     public int IntervalTime 
     { 
      get; 
      set; 
     } 

     public Timer Timer 
     { 
      get; 
      set; 
     } 

     public StateInfo SI 
     { 
      get; 
      set; 
     } 

     public int Treshold 
     { 
      get; 
      private set; 
     } 

     public void TimerCallbackWorker(object state) 
     { 
      var si = state as StateInfo; 

      if (si == null) 
      { 
       throw new ArgumentNullException("state"); 
      } 

      si.ExecutionCounter++; 

      if (si.ExecutionCounter > this.Treshold) 
      { 
       this.Timer.Change(Timeout.Infinite, Timeout.Infinite); 
       Console.WriteLine("-Timer stop, execution reached treshold {0}", this.Treshold); 
      } 
      else 
      { 
       Console.WriteLine("{0} lapse, Time {1}", si.ExecutionCounter, si.ToString()); 
      } 
     } 

     public class StateInfo 
     { 
      public int ExecutionCounter 
      { 
       get; 
       set; 
      } 

      public DateTime LastRun 
      { 
       get 
       { 
        return DateTime.Now; 
       } 
      } 

      public override string ToString() 
      { 
       return this.LastRun.ToString(); 
      } 
     } 
    } 

    // Result: 
    // 
    // 1 lapse, Time 2015-02-13 01:28:39 AM 
    // 2 lapse, Time 2015-02-13 01:28:41 AM 
    // -Timer stop, execution reached treshold 2 
    // 
+0

Suggerirei di usare un programma di formattazione del codice o qualcosa per riordinare quel codice, re. spaziatura, formattazione, ecc. Può essere meraviglioso, ma le prime impressioni contano, e la mia prima impressione è che non vorrei usare il codice in questo modo. – ProfK

+0

È stato un esempio rapido ma funzionante, spero che ora sia più leggibile. – BTE

Problemi correlati