2011-11-22 12 views
19

Sto usando System.Timers.Timer nella mia applicazione WPF. Voglio capire come si comporta il timer, dopo che il computer è ibernato, e dormire. Sto ricevendo alcuni strani problemi con la mia applicazione, dopo che il computer è stato ripreso dall'ibernazione.Comportamento di System.Timers.Timer nell'applicazione WPF, dopo la sospensione e la sospensione?

Come devo gestire i timer e come si comportano quando il computer è in modalità ibernazione/sospensione?

Ho un timer di mezzanotte che dovrebbe funzionare ogni mezzanotte per ripristinare i valori predefiniti sull'interfaccia utente.

Ecco il codice che crea il timer:

private void ResetMidnightTimer() 
     { 
      // kill the old timer 
      DisposeMidnightTimer(); 

      _midnightTimer = new Timer(); 
      // scheduling the timer to elapse 1 minute after midnight 
      _midnightTimer.Interval = (DateTime.Today.AddDays(1).AddMinutes(1) - DateTime.Now).TotalMilliseconds; 
      _midnightTimer.Elapsed += (_, __) => UpdateRecommendedCollectingTime(); 
      _midnightTimer.Enabled = true; 
      _midnightTimer.Start(); 
     } 

Su contructor di pagina dell'interfaccia utente, chiamo il metodo che chiama ResestMidnightTimer() e crea il timer de facto. Dopo che il timer aspetta solo la notte.

Quando arriva la notte (in realtà sono le 12:01), il timer funziona, ripristina i valori predefiniti come previsto e quindi dispone il timer esistente. Alla fine crea un nuovo timer di mezzanotte per il giorno successivo. Ma se provo a sospendere il computer durante quel giorno, il timer di mezzanotte non funzionerà e non ripristineremo i valori predefiniti.

È perché, durante l'ibernazione, semplicemente posticipa la gestione degli eventi per lo stesso periodo di tempo in cui è stata ibernata?

+6

Quali sono i problemi che stai vedendo (solo curiosi)? – M4N

+0

@ M4N Ho modificato la domanda. – User1234

risposta

14

Questo dipende da come si utilizzano i timer. Se li stai utilizzando per avviare un evento che si verifica raramente (più di un paio di minuti), probabilmente vedrai un comportamento "strano". Dato che non si specifica il comportamento "strano", assumerò che il timer del programma si spenga più tardi di quanto dovrebbe.

Spiegazione: Il problema con l'andare a dormire/in letargo è che tutti i programmi sono sospesi. Ciò significa che i tuoi Timer non vengono aggiornati e quindi quando dormi/ibernati e ritorni, è come se fossi stato congelato per quel periodo di tempo in cui dormivi/ibernavi. Ciò significa che se un timer è impostato per spegnersi in un'ora e il computer va in modalità di sospensione al contrassegno da 15 minuti, una volta riattivato saranno necessari altri 45 minuti, indipendentemente dal tempo di sospensione del computer.

Soluzione: Una correzione consisteva nel mantenere un DateTime attorno all'ultima volta in cui si è verificato l'evento. Quindi, fare in modo che un timer si spenga periodicamente (ogni 10 secondi o 10 minuti, a seconda della precisione desiderata) e controllare il DateTime dell'ultima esecuzione. Se la differenza tra ora e l'ultimo tempo di esecuzione è maggiore o uguale all'intervallo desiderato, ALLORA esegui l'esecuzione.

Questo lo aggiusterà in modo tale che se un evento "dovrebbe" essersi verificato durante il sonno/ibernazione, inizierà nel momento in cui ritorni dal sonno/ibernazione.

Aggiornamento: La soluzione illustrata sopra funzionerà e inserirò un paio di dettagli per aiutarti a implementarlo.

  • Anziché creare/smaltimento di nuovi temporizzatori, crea ONE timer con l'uso che si ricorrenti (la proprietà AutoReset vale true)

  • L'intervallo del singolo timer dovrebbe NON essere impostato in base alla prossima volta che l'evento dovrebbe verificarsi. Al contrario, dovrebbe essere impostato su un valore che si sceglie che rappresenterà la frequenza di polling (quanto spesso si verifica per vedere se il 'caso' dovrebbe funzionare). La scelta dovrebbe essere un equilibrio tra efficienza e precisione. Se è necessario eseguirlo VERAMENTE vicino alle 12:01, quindi impostare l'intervallo su circa 5-10 secondi. Se è meno importante che sia esattamente alle 00:01, puoi aumentare l'intervallo a qualcosa come 1-10 minuti.

  • È necessario mantenere una certa DataTime di quando si è verificata l'ultima esecuzione O quando si verifica l'esecuzione successiva. Preferirei 'quando la prossima esecuzione dovrebbe avvenire' in modo che non si sta facendo (LastExecutionTime + EventInterval) ogni volta che il timer è trascorso, devi semplicemente essere confrontando l'ora corrente e il tempo dovrebbe verificarsi l'evento.

  • Una volta che il timer è trascorso e l'evento DEVONO verificarsi (da qualche parte intorno 12:01), è necessario aggiornare la stored DateTime e quindi eseguire il codice che si desidera eseguire al 12:01.

sonno vs. Hibernate Chiarimento: La differenza principale tra il sonno e Hibernate è che nel sonno, tutto è tenuto in RAM, mentre ibernazione salva lo stato corrente su disco. Il principale vantaggio dell'ibernazione è che la RAM non ha più bisogno di energia e quindi consuma meno energia. Questo è il motivo per cui si consiglia di utilizzare hibernate nel corso del sonno quando si tratta di computer portatili o altri dispositivi che utilizzano una quantità finita di energia.

Ciò detto, non v'è alcuna differenza nell'esecuzione dei programmi mentre vengono sospesi in entrambi i casi. Purtroppo, lo System.Timers.Timer non 'sveglia' un computer e quindi non si può far rispettare il codice da eseguire a ~ 12: 01:00.

Credo che ci siano ALTRI modi per "svegliare" un computer, ma a meno che non si segua questa strada il meglio che si possa fare è eseguire il tuo "evento" durante il prossimo "evento di polling" del timer dopo che è uscito dal sonno/hibernate.

+0

+1 per l'esame del suggerimento di stato – Unsliced

+0

+1 per il controllo della differenza di orario. – PRR

+0

@docmanhattan Grazie mille !!!! Ho modificato la domanda e ho aggiunto ulteriori dettagli. Cosa pensi che potresti risolvere in questo caso? – User1234

1

System.Timers.Timer è un timer basato su server (l'evento trascorso utilizza Threadpool. Più preciso di altri timer). Quando il computer entra in modalità di sospensione o in modalità di ibernazione, tutti gli stati del programma vengono memorizzati nella RAM. Lo stesso vale per lo stato della tua applicazione. Una volta che il sistema è attivo, lo stato dell'applicazione verrà ripristinato (insieme al timer) dal sistema operativo. Non è una buona idea "fare qualcosa" o provare a rilevare questi eventi. È comunque possibile da un servizio di Windows. Lasciare al sistema operativo per fare il suo lavoro.

3

È perché, durante l'ibernazione, semplicemente posticipa la gestione degli eventi per lo stesso periodo di tempo in cui è stata ibernata?

Mentre il computer è in modalità sospesa (cioè sospensione o ibernazione), non fa nulla. Ciò comprende, in particolare, lo scheduler che gestisce il risveglio il filo che sta monitorando la coda di eventi timer non è in esecuzione, e così quel filo non sta facendo alcun progresso verso di riprendere l'esecuzione per gestire il timer successivo.

Non è tanto che l'evento è esplicitamente posticipato a. Ma sì, questo è l'effetto netto.

In alcuni casi, è possibile utilizzare una classe di timer che non presenta questo problema. Entrambi System.Windows.Forms.Timer e System.Windows.Threading.DispatcherTimer non si basano sul programmatore di thread di Windows, ma sul messaggio WM_TIMER. A causa del modo in cui questo messaggio funziona — viene generato "al volo" quando il loop di messaggi di un thread controlla la coda dei messaggi, in base alla scadenza del tempo di scadenza per il timer & hellip; in un certo senso, è simile al lavoro di polling descritto in the other answer to your question — è immune ai ritardi che sarebbero altrimenti causati dal computer in sospensione.

Hai dichiarato che lo scenario riguarda un programma WPF, quindi potresti trovare che la soluzione migliore è in realtà utilizzare la classe DispatcherTimer anziché System.Timers.Timer.

Se si decide avete bisogno di un'implementazione del timer che non è legato al thread UI, ecco una versione di System.Threading.Timer che correttamente tenere conto trascorrere del tempo mentre sospeso:

class SleepAwareTimer : IDisposable 
{ 
    private readonly Timer _timer; 
    private TimeSpan _dueTime; 
    private TimeSpan _period; 
    private DateTime _nextTick; 
    private bool _resuming; 

    public SleepAwareTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) 
    { 
     _dueTime = dueTime; 
     _period = period; 
     _nextTick = DateTime.UtcNow + dueTime; 
     SystemEvents.PowerModeChanged += _OnPowerModeChanged; 

     _timer = new System.Threading.Timer(o => 
     { 
      _nextTick = DateTime.UtcNow + _period; 
      if (_resuming) 
      { 
       _timer.Change(_period, _period); 
       _resuming = false; 
      } 
      callback(o); 
     }, state, dueTime, period); 
    } 

    private void _OnPowerModeChanged(object sender, PowerModeChangedEventArgs e) 
    { 
     if (e.Mode == PowerModes.Resume) 
     { 
      TimeSpan dueTime = _nextTick - DateTime.UtcNow; 

      if (dueTime < TimeSpan.Zero) 
      { 
       dueTime = TimeSpan.Zero; 
      } 

      _timer.Change(dueTime, _period); 
      _resuming = true; 
     } 
    } 

    public void Change(TimeSpan dueTime, TimeSpan period) 
    { 
     _dueTime = dueTime; 
     _period = period; 
     _nextTick = DateTime.UtcNow + _dueTime; 
     _resuming = false; 
     _timer.Change(dueTime, period); 
    } 

    public void Dispose() 
    { 
     SystemEvents.PowerModeChanged -= _OnPowerModeChanged; 
     _timer.Dispose(); 
    } 
} 

L'interfaccia pubblica per System.Threading.Timer e l'interfaccia sottoinsieme copiata da quella classe, è diversa da quella che si trova su System.Timers.Timer, ma ha lo stesso risultato. Se vuoi davvero una classe che funzioni esattamente come System.Timers.Timer, non dovrebbe essere difficile adattare la tecnica di cui sopra in base alle tue esigenze.

Problemi correlati