DispatcherTimer und UI-Aktualisierungslimits in C # silverlight

8

Auch hier entschuldige ich mich für eine Frage, die für Sie alle einfach sein könnte. Ich habe ein begrenztes Verständnis dafür, was hinter den Kulissen in Silverlight steckt.

Ich habe eine Diagramm-App (Visiblox), die ich als rollenden Bereich alle 20 ms aktualisiere, indem ich einen Punkt hinzufüge und entferne. Im Pseudocode:

%Vor%

Beim Ausführen von 6 Serien in diesem Charting-Tool wurde es langsam träge. Das Ändern des Ticks auf 10ms machte keinen Unterschied, das Diagramm wurde mit der gleichen Geschwindigkeit aktualisiert, daher scheint es, dass 20ms das Tempolimit ist (UI oder Diagramm?).

Ich habe versucht mit CompositionTarget.Rendering und habe die gleichen Ergebnisse: unter 20ms gab es keinen Unterschied in der Geschwindigkeit.

Dann habe ich versehentlich beide aktiviert und die Geschwindigkeit verdoppelt. Also habe ich mit mehreren Threads (2, 3, 4) getestet und die Geschwindigkeit verdoppelt, verdreifacht und vervierfacht. Dies hat noch keine Sperren, da ich nicht einmal weiß, welchen Prozess ich brauche, um eine Sperre zu erzeugen, aber keine Datenbeschädigung oder Speicherlecks hat.

Die Frage, die ich habe, ist, warum ein träges Diagramm bei 20ms nicht bei 10ms laufen kann, aber lächerlich schnell ist, wenn es Multithread ist? Wird der Aktualisierungsvorgang der Benutzeroberfläche schneller ausgeführt? Wird die Diagrammberechnung verdoppelt? Oder gibt es ein Limit für die Geschwindigkeit, mit der ein einzelner DispatcherTimer ausgeführt werden kann?

Danke!

Bearbeiten: Ich habe einen Hintergrund der eingebetteten Codierung, wenn ich also an Threads und Timings denke, denke ich sofort daran, einen Pin in der Hardware umzuschalten und ein Oszilloskop anzuschließen, um die Prozesslängen zu messen. Ich bin neu in Threads in C # und es gibt keine Pins, um Scopes zu verbinden. Gibt es eine Möglichkeit, Thread-Timings grafisch zu sehen?

    
PaulG 12.01.2011, 01:25
quelle

2 Antworten

4

Der Schlüssel hier ist, glaube ich, zu erkennen, dass Silverlight standardmäßig mit einer maximalen Bildrate von 60 fps rendert (anpassbar durch Ihre MaxFrameRate-Eigenschaft). Das bedeutet, dass die Ticker des DispatcherTimers maximal 60 Mal pro Sekunde ausgelöst werden. Darüber hinaus erfolgt die gesamte Rendering-Arbeit auch auf dem UI-Thread, so dass der DispatcherTimer mit der Geschwindigkeit ausgelöst wird, mit der die Zeichnung am besten funktioniert, wie das vorherige Poster gezeigt hat.

Das Ergebnis dessen, was Sie tun, indem Sie drei Timer hinzufügen, besteht darin, die "add data" -Methode dreimal pro Ereignisschleife anstatt einmal auszulösen. Es sieht so aus, als würden Ihre Diagramme viel schneller, aber tatsächlich der Frame Rate ist in etwa gleich. Sie können den gleichen Effekt mit einem einzigen DispatcherTimer erzielen und einfach 3 Mal so viele Daten für jeden Tick hinzufügen. Sie können dies überprüfen, indem Sie sich in das CompositionTarget.Rendering-Ereignis einklinken und die Bildrate dort parallel zählen.

Der zuvor erstellte ObservableCollection-Punkt ist ein guter, aber in Visiblox gibt es ein bisschen Magie, um die Auswirkungen so zu mildern Wenn Sie Daten mit sehr hoher Geschwindigkeit hinzufügen, werden die Diagrammaktualisierungen mit der Rate der Renderschleife hochgezählt, und unnötige erneute Rendervorgänge werden verworfen.

Auch in Bezug auf Ihren Punkt bezüglich der Anbindung an die ObservableCollection-Implementierung von IDataSeries können Sie die IDataSeries-Schnittstelle vollständig selbst implementieren, indem Sie sie beispielsweise mit einer einfachen Liste unterstützen. Seien Sie sich dessen bewusst, dass das Diagramm bei Änderungen der Daten nicht mehr automatisch aktualisiert wird. Sie können eine Diagrammaktualisierung erzwingen, indem Sie Chart.Invalidate () aufrufen oder einen manuell festgelegten Achsenbereich ändern.

    
wjbeau 14.01.2011, 09:46
quelle
7

Ein DispatcherTimer, der sein Tick-Ereignis im UI-Thread auslöst, wird als Timer mit niedriger oder niedriger Genauigkeit betrachtet, da sein Intervall effektiv "Tick nicht früher als x seit dem letzten Tick" bedeutet. Wenn der UI-Thread beschäftigt ist, irgendetwas auszuführen (Verarbeitung der Eingabe, Aktualisieren des Diagramms usw.), werden die Ereignisse des Timers verzögert. Darüber hinaus verlangsamt sich das DispatcherTimer-Verhalten in sehr kurzen Intervallen auf dem Benutzeroberflächenthread, wodurch die Reaktionsfähigkeit Ihrer Anwendung verlangsamt wird, da die Anwendung nicht auf Eingaben reagieren kann, während das Tick-Ereignis ausgelöst wird.

Wie Sie bereits angemerkt haben, sollten Sie, um Daten häufig zu verarbeiten, zu einem Hintergrundthread wechseln. Aber es gibt Vorbehalte. Die Tatsache, dass Sie derzeit keine Korruption oder andere Fehler beobachten, könnte rein zufällig sein. Wenn die Liste gleichzeitig in einem Hintergrund-Thread geändert wird und der Vordergrund-Thread versucht, daraus zu lesen, stürzen Sie sich (wenn Sie Glück haben) oder sehen beschädigte Daten.

In Ihrem Beispiel haben Sie einen Kommentar, der besagt: "Sie müssen das Diagramm nicht aktualisieren, es wird automatisch aktualisiert." Das lässt mich fragen, woher weiß das Diagramm, dass Sie die datapoints Sammlung geändert haben? List<T> löst keine Ereignisse aus, wenn es geändert wird. Wenn Sie ObservableCollection<T> verwenden, würde ich darauf hinweisen, dass Sie jedes Mal, wenn Sie einen Punkt entfernen / hinzufügen, das Diagramm möglicherweise aktualisieren, was die Geschwindigkeit verlangsamen könnte.

Aber wenn Sie tatsächlich List<T> verwenden, muss etwas anderes (vielleicht ein anderer Timer?) das Diagramm aktualisieren. Vielleicht hat das Diagrammsteuerelement selbst einen integrierten Auto-Refresh-Mechanismus?

Auf jeden Fall ist das Problem ein wenig knifflig aber nicht ganz neu . Es gibt Möglichkeiten, wie Sie eine Sammlung in einem Hintergrund-Thread verwalten und über den UI-Thread an diese binden können. Je schneller die Benutzeroberfläche aktualisiert wird, desto wahrscheinlicher ist es, dass Sie darauf warten, dass ein Hintergrund-Thread eine Sperre freigibt.

Eine Möglichkeit, dies zu minimieren, wäre die Verwendung von LinkedList<T> anstelle von List<T> . Zum Ende einer LinkedList wird O (1) hinzugefügt, also wird ein Element entfernt. A List<T> muss alles um eins nach unten verschieben, wenn Sie ein Objekt von Anfang an entfernen. Wenn Sie LinkedList verwenden, können Sie es in den Hintergrundthreads sperren und die Zeit, in der Sie die Sperre halten, minimieren. Im Benutzeroberflächenthread müssten Sie auch dieselbe Sperre erhalten und entweder die Liste in ein Array kopieren oder das Diagramm aktualisieren, während die Sperre gehalten wird.

Eine andere mögliche Lösung wäre es, "Chunks" von Punkten im Hintergrund-Thread zu puffern und einen Stapel von ihnen mit Dispatcher.BeginInvoke an den UI-Thread zu senden, wo Sie dann eine Sammlung sicher aktualisieren könnten.

    
Josh 12.01.2011 01:59
quelle