2011-01-12 8 views
8

Ancora una volta mi scuso per una domanda che potrebbe essere semplice per tutti voi. Ho una comprensione limitata di ciò che va dietro le quinte di Silverlight.DispatcherTimer e limiti di aggiornamento dell'interfaccia utente in C# silverlight

Ho un'app per la creazione di grafici (Visiblox) che utilizzo come ambito rotante aggiornato ogni 20 ms, aggiungendo e rimuovendo un punto. In pseudocodice:

List<Point> datapoints= new List<Point>(); 
Series series = new Series(datapoints); 
void timer_tick(){ 
    datapoints.Add(new Point); 
    datapoints.RemoveAt(0); 
    // no need to refresh chart, it does refresh automatically 
} 

Durante l'esecuzione di 6 serie in questo strumento di creazione di grafici, ha iniziato a mostrare un po 'lenta. Cambiare il segno di spunta a 10 ms non ha fatto alcuna differenza, il grafico è stato aggiornato alla stessa velocità, quindi sembra che 20 ms sia il limite di velocità (UI o grafico?).

Ho provato con CompositionTarget.Rendering e ho ottenuto gli stessi risultati: sotto 20 ms non c'era differenza di velocità.

Quindi ho accidentalmente abilitato entrambi e la velocità è raddoppiata. Così ho provato con più thread (2, 3, 4) e la velocità è raddoppiata, triplicata e quadruplicata. Questo non ha ancora serrature, in quanto non so nemmeno quale processo ho bisogno per generare un blocco, ma non ho ottenuto alcun danno o perdita di memoria.

La domanda che ho è perché un grafico lento a 20 ms non può essere eseguito a 10 ms ma è ridicolmente veloce quando multithreaded? Il processo di aggiornamento dell'interfaccia utente viene eseguito più velocemente? Il calcolo del grafico è raddoppiato? O c'è un limite alla velocità con cui un singolo DispatcherTimer può essere eseguito?

Grazie!


Edit: Ho una formazione di codifica incorporato, per cui quando penso di fili e tempi, penso subito commutando un perno in hardware e agganciare un ambito per misurare lunghezze di processo. Sono nuovo ai thread in C# e non ci sono pin per collegare gli ambiti. C'è un modo per vedere i tempi dei thread graficamente?

risposta

4

La chiave qui penso è di rendersi conto che Silverlight esegue il rendering ad un frame rate massimo di 60fps per impostazione predefinita (personalizzabile tramite la proprietà MaxFrameRate). Ciò significa che i tick di DispatcherTimer scatteranno al massimo 60 volte al secondo. Inoltre, tutto il lavoro di rendering avviene anche sul thread dell'interfaccia utente, quindi il DispatcherTimer si attiva alla velocità con cui il disegno si sta verificando al meglio, come indicato dal poster precedente.

Il risultato di ciò che si sta facendo aggiungendo tre timer è solo per attivare il metodo "aggiungi dati" 3 volte per ciclo di eventi piuttosto che una sola volta, quindi sembrerà che i tuoi grafici stiano andando molto più velocemente, ma di fatto la frequenza fotogrammi è all'incirca la stessa. È possibile ottenere lo stesso effetto con un singolo DispatcherTimer e aggiungere solo 3 volte più dati su ogni Tick. Puoi verificarlo agganciando l'evento CompositionTarget.Rendering e contando il frame rate in parallelo.

Il punto ObservableCollection creato in precedenza è buono, ma in Visiblox c'è un po 'di magia per cercare di attenuarne gli effetti, quindi se si aggiungono dati a una velocità molto elevata gli aggiornamenti del grafico verranno raggruppati in la frequenza del ciclo di rendering e i ri-rendering non necessari verranno eliminati.

Anche per quanto riguarda il vostro punto di essere legati all'implementazione di ObservateCollection di IDataSeries, siete completamente liberi di implementare l'interfaccia IDataSeries da soli, ad esempio sostenendola con una semplice lista. Tieni presente che, ovviamente, se lo fai, il grafico non si aggiornerà più automaticamente quando i dati cambiano. È possibile forzare l'aggiornamento di un grafico chiamando Chart.Invalidate() o modificando un intervallo di assi impostato manualmente.

+0

Hai ragione. È un'illusione. A basse velocità il "rolling" del grafico è più facile da tracciare con gli occhi e la lentezza è facilmente rilevabile, ma aggiornando più di un punto alla volta, la lentezza è difficile da vedere. Grazie! – PaulG

7

Un DispatcherTimer, che attiva il proprio evento Tick sul thread dell'interfaccia utente, è considerato un timer a bassa risoluzione o a bassa precisione perché il suo intervallo indica effettivamente "tick non prima di x dall'ultimo tick". Se il thread dell'interfaccia utente è occupato a fare nulla (elaborazione dell'elaborazione, aggiornamento del grafico, ecc.), Quindi ritarderà gli eventi del timer. Inoltre, avere un po 'di ticker di DispatcherTimer sul thread dell'interfaccia utente a intervalli molto bassi rallenta anche la reattività dell'applicazione perché mentre l'evento Tick viene sollevato, l'applicazione non può rispondere all'input.

Come è stato notato, per elaborare i dati frequentemente, è necessario passare a un thread in background. Ma ci sono dei caveat. Il fatto che al momento non si osservi corruzione o altri bug potrebbe essere puramente casuale. Se l'elenco viene modificato su un thread in background contemporaneamente al thread in primo piano che sta cercando di leggerlo, si verificherà un arresto anomalo (se si è fortunati) o si vedranno dati corrotti.

Nel tuo esempio, hai un commento che dice "non è necessario aggiornare il grafico, si aggiorna automaticamente". Questo mi fa domandare in che modo il grafico sa che hai modificato la collezione datapoints? List<T> non genera eventi quando viene modificato. Se si stesse utilizzando un ObservableCollection<T>, vorrei sottolineare che ogni volta che si rimuove/aggiungi un punto si sta potenzialmente aggiornando il grafico, il che potrebbe rallentare le cose.

Ma se in effetti si utilizza List<T>, deve esserci qualcos'altro (forse un altro timer?) Che sta aggiornando il grafico. Forse il controllo grafico stesso ha un meccanismo di aggiornamento automatico incorporato?

In ogni caso, il problema è un po 'complicato but not completely new. Esistono modi in cui è possibile mantenere una raccolta su un thread in background e collegarla dal thread dell'interfaccia utente. Ma più velocemente la tua interfaccia utente viene aggiornata, più è probabile che ti aspetteresti che un thread in background rilasci un blocco.

Un modo per ridurre al minimo questo sarebbe utilizzare LinkedList<T> anziché List<T>. L'aggiunta alla fine di una LinkedList è O (1), quindi la rimozione di un elemento. A List<T> è necessario spostare tutto di un livello quando si rimuove un oggetto dall'inizio. Usando LinkedList puoi bloccarlo nei thread in background e ridurrai al minimo il tempo di blocco. Sul thread dell'interfaccia utente è anche necessario ottenere lo stesso blocco e copiare l'elenco in un array o aggiornare il grafico mentre il blocco viene mantenuto.

Un'altra possibile soluzione sarebbe quella di bufferizzare "blocchi" di punti sul thread in background e postarne un batch sul thread dell'interfaccia utente con Dispatcher.BeginInvoke, dove è possibile quindi aggiornare in modo sicuro una raccolta.

+0

Grazie per la grande intuizione. Per rispondere ad alcune delle tue preoccupazioni, non sto usando esattamente un elenco <>, ma un'implementazione di una raccolta che ha INotifyCollectionChanged con esso. Poiché si tratta di un'app di terze parti, non c'è molto che posso fare al riguardo. Il mio primo approccio a questo è stato esattamente come hai suggerito, stavo rendendo una bitmap in background mentre stavo scorrendo una bitmap in primo piano. Ma a causa dei limiti di tempo e della qualità del mio strumento di creazione di grafici "fatto in casa", ho optato per uno di terze parti. Quindi LinkedList e altri sono fuori, devo usare la loro specifica implementazione ... – PaulG

+0

(continua, a corto di caratteri) Un'altra cosa, ho appena realizzato che System.Windows.Threading crea thread che girano nello stesso thread dell'interfaccia utente, altrimenti non sarebbe possibile aggiornare l'interfaccia utente. Destra? Quindi il fatto che stavo pensando di essere multithreading dovrebbe essere errato. Cosa sta succedendo allora? Silverlight assegna sezioni temporali per condividere tra interfaccia utente e DispatchTimer e il fatto che ne abbia create diverse aumenta la priorità delle attività del timer? – PaulG

+0

(continua III) Questo è anche il motivo per cui penso di non vedere crash o bug, perché in realtà non sto eseguendo più thread ma solo uno dopo l'altro.La mia applicazione è uno strumento di simulazione che elabora migliaia di calcoli a matrice per periodo di tempo, con conseguenti milioni di calcoli al secondo. Dovrei essermi schiantato ormai. Perfavore, correggimi se sbaglio. Queste sono tutte ipotesi. – PaulG

Problemi correlati