2009-10-10 12 views
5

Ho un'app WPF che utilizza DispatcherTimer per aggiornare un segno di spunta dell'orologio.DispatcherTimer che consuma CPU nel tempo causando il rendering non corretto del visual WPF

Tuttavia, dopo che la mia applicazione è stata in funzione per circa 6 ore, gli angoli delle lancette dell'orologio non cambiano più. Ho verificato che DispatcherTimer stia ancora funzionando con Debug e che i valori dell'angolo siano ancora in fase di aggiornamento, tuttavia il rendering della schermata non riflette la modifica.

Ho anche verificato utilizzando gli strumenti WPFPerf Visual Profiler che il Tempo non contrassegnato, Tick (Time Manager) e AnimatedRenderMessageHandler (Contenuto multimediale) stanno gradualmente crescendo fino a quando non consumano quasi l'80% della CPU, tuttavia la memoria è stabile.

Il hHandRT.Angle è un riferimento ad un RotateTransform

hHandRT = new RotateTransform(_hAngle); 

Questo codice funziona perfettamente per circa 5 ore di marcia in rettilineo, ma dopo che ritardi e la variazione dell'angolo non rende allo schermo. Qualche suggerimento su come risolvere questo problema o eventuali soluzioni che potresti conoscere.

.NET 3.5, Windows Vista SP1 o Windows XP SP3 (entrambi mostrano lo stesso comportamento)

EDIT: Aggiunta di Clock Tick Funzione

//In Constructor 
... 
_dt = new DispatcherTimer(); 
_dt.Interval = new TimeSpan(0, 0, 1); 
_dt.Tick += new EventHandler(Clock_Tick); 
... 

private void Clock_Tick(object sender, EventArgs e) 
     { 

      DateTime startTime = DateTime.UtcNow; 
      TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById(_timeZoneId); 
      _now = TimeZoneInfo.ConvertTime(startTime, TimeZoneInfo.Utc, tst); 
      int hoursInMinutes = _now.Hour * 60 + _now.Minute; 
      int minutesInSeconds = _now.Minute * 60 + _now.Second; 
      _hAngle = (double)hoursInMinutes * 360/720; 
      _mAngle = (double)minutesInSeconds * 360/3600; 
      _sAngle = (double)_now.Second * 360/60; 
      // Use _sAngle to showcase more movement during Testing. 
      //hHandRT.Angle = _sAngle; 
      hHandRT.Angle = _hAngle; 
      mHandRT.Angle = _mAngle; 
      sHandRT.Angle = _sAngle; 

      //DSEffect 
      // Add Shadows to Hands creating a UNIFORM light 
      //hands.Effect = textDropShadow; 
     } 

Lungo le linee di troppo accadendo nel clock tick, attualmente sto provando questo aggiustamento per vedere se aiuta. Peccato ci vogliono 5 ore per il bug di manifestarsi :(

//DateTime startTime = DateTime.UtcNow; 
    //TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById(_timeZoneId); 
    //_now = TimeZoneInfo.ConvertTime(startTime, TimeZoneInfo.Utc, tst); 
    _now = _now.AddSeconds(1); 
+0

Non conosco la risposta, ma questo è un problema curioso. Non vedo l'ora di vedere alcune risposte. –

+0

Con quale frequenza spunta il timer del dispatcher? –

+0

@Greg Ho aggiunto anche quella parte. Si aggiorna ogni secondo. – discorax

risposta

4

Hai detto che stai creando un'istanza di Classi di clock ogni volta? Si noti che i timer di .NET si radicheranno per non essere raccolti e continueranno a sparare finché non li si interrompe e manterranno gli oggetti Clock vivi perché vengono referenziati nel timer ti evento ck.

Penso che quello che sta succedendo è che con ogni orologio che crei inizi un altro timer. All'inizio fai solo 1 evento al secondo, ma poi aggiungi un altro timer e ottieni 2 al secondo, e continuano ad accumularsi in questo modo. Alla fine vedrai il tuo gestore di Tick e AnimatedRenderMessageHandler in aumento nella CPU finché non si impantaneranno e non saranno in grado di aggiornare lo schermo. Ciò spiegherebbe anche perché l'aumento della frequenza degli spari temporizzati fa apparire i sintomi prima.

La correzione deve essere semplice: basta interrompere o disporre DispatcherTimer quando hai finito con l'oggetto Orologio.

+0

Ho ricreato la classe Clock per includere un metodo DisposeTimer che chiamo prima di rimuovere l'istanza di Clock dalla Struttura visiva. Sto facendo un test adesso per vedere se questo risolve il problema. Tengo le dita incrociate. – discorax

+0

una rielaborazione della classe Clock sembra aver risolto alcuni dei problemi. Dal momento che ho messo una taglia su questa domanda ho dovuto accettare una risposta, tuttavia, il problema di DispatcherTimer è ancora in sospeso. Speravo che qualcuno con esperienza di lavoro con più thread sarebbe intervenuto, ma non lo sarebbe stato. Grazie per il vostro aiuto. – discorax

+0

Che cosa ti fa pensare che sia un problema con DispatcherTimer stesso, piuttosto che il numero di quelli che hai in giro? Hai controllato la frequenza con cui viene chiamato il tuo metodo Clock_Tick?DispatcherTimer è piuttosto solido: a intervalli regolari, mette semplicemente un metodo sulla coda per il thread dell'interfaccia utente da richiamare. Se la tua app si blocca a causa dell'eccessivo uso della CPU sull'interfaccia utente e del rendering dei thread, non è più un problema multi-threading, è più una questione di "perché sto invocando così tante chiamate al thread dell'interfaccia utente?" – RandomEngy

1

Stai ammesso che sia il DispatcherTimer e concentrandosi totalmente su questo. Personalmente ho difficoltà a credere che abbia qualcosa a che fare con il timer in sé ma pensa di avere a che fare con qualsiasi cosa tu stia facendo nel segno di spunta del timer. Puoi dirci di più su quello che succede ogni volta che il timer ticchetta?

+0

divertente, dovresti dirlo. Questo è esattamente il punto in cui ho iniziato, infatti la mia prima domanda ha avuto esattamente questo, quindi la aggiungerò di nuovo. – discorax

+0

Grazie per aver aggiunto quel dettaglio. Quindi stai praticamente cambiando il valore degli angoli, dubito molto che questo sia causa di problemi. È possibile modificare il timer in modo che si attivi sempre il 10 o il 100 ° di secondo? Se davvero ha qualcosa a che fare con questo ciclo del timer che potrebbe aiutare ad accelerare il processo, quindi è molto più facile da riprodurre. –

+0

Questo è un buon suggerimento. L'ho fatto e sembra accelerare il problema. C'è un altro dettaglio che sto creando ogni volta un'istanza della classe Clock, questo è il costruttore a cui mi riferisco. È possibile che l'oggetto Clock non venga raccolto correttamente da GC, il che potrebbe essere il colpevole. – discorax

1
hHandRT.Angle = _hAngle; 
mHandRT.Angle = _mAngle; 
sHandRT.Angle = _sAngle; 

Credo che si debba guardare ancora il codice sopra.

Si sta impostando la proprietà Angolo della propria trasformazione per tutte e 3 le trasformazioni anche se non si desidera che cambino ogni secondo. Il tuo minuto cambierà ogni 60 cambiamenti e la tua ora cambierà ogni 3600 secondi. Tuttavia puoi almeno ridurre l'angolo delle ore di cambio per ogni secondo.

Ciò che accade qui è che ogni volta che si richiedono le modifiche di trasformazione a WPF, WPF accoda la richiesta alla coda di invio prioritaria e ogni secondo si sta spingendo più modifiche da fare, quindi può elaborare. E questo è l'unico motivo per cui l'utilizzo della CPU continua ad aumentare anziché la memoria.

Analisi dettagliata:

Dopo aver guardato il codice, sento il tuo evento DispatcherTimer_Tick fa troppo di calcolo, ricordate filo Dispatcher è già sovraccarico di un sacco di cose da fare come gestire il routing eventi, aggiornamento visivo, ecc, se mantieni la tua CPU più impegnata a svolgere attività personalizzate nel thread di dispatcher che anche in ogni secondo evento, continuerà sicuramente ad aumentare la coda delle attività in sospeso.

Si potrebbe pensare che sia un piccolo calcolo di moltiplicazione ma per il thread di Dispatcher può essere costoso quando si tratta di caricare fusi orari, convertire il valore temporale ecc. Si dovrebbe profilare e vedere il tempo di esecuzione del tick.

È necessario utilizzare l'oggetto System.Threading.Timer, che verrà eseguito su un altro thread, dopo ogni evento di tick, una volta terminati i calcoli degli angoli finali richiesti, è possibile passarli sul thread di Dispatcher.

come,

Dispatcher.BeginInvoke((Action)delegate(){ 
    hHandRT.Angle = _hAngle; 
    mHandRT.Angle = _mAngle; 
    sHandRT.Angle = _sAngle; 
}); 

In questo modo, si sarà riducendo il carico di lavoro a partire da fili dispatcher po '.

+0

Sono d'accordo sul fatto che le prestazioni sarebbero aiutate dal fatto che non stanno facendo tutto il lavoro sul thread di dispatcher, ma non sembra affrontare direttamente i problemi del post originale. A lungo termine questa potrebbe essere una buona idea, ma sospetto che ci sia in realtà più overhead nel cambiare gli angoli rispetto alla matematica di date che sta succedendo sopra, quindi potrebbe essere un po 'di over-optimization. –

+0

È possibile definire e scoprire la lunghezza della coda dell'oggetto dispatcher come, ma PriorityQueue dell'oggetto dispatcher è membro privato, quindi non so come visualizzarlo. Ma sono sicuro che i termini più lunghi potrebbero essere un'idea migliore perché si sta sicuramente spingendo di più nella coda del dispatcher che poi può gestire. Ho lavorato in profondità con i thread di dispatcher, a volte anche 2-3 mill secondi di codice su thread diversi fanno poca differenza, non lo noterai finché non si accumula per ore. –

+0

La quantità di lavoro in questo esempio eseguita nel metodo Dispatcher è assolutamente banale. Ho preso in giro un programma simile e ho fatto scoppiare l'evento ogni millisecondo e il programma rimaneva al di sotto dell'1% di utilizzo medio della CPU. Sono d'accordo con Drew: concentrarsi sull'uso della CPU del metodo tick è un'ottimizzazione prematura. – RandomEngy