2011-01-12 12 views
8

Wieder 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 in Silverlight hinter den Kulissen passiert.DispatcherTimer und UI aktualisieren Grenzen in C# silverlight

Ich habe eine Diagramm-App (Visiblox), die ich als Rolling Scope alle 20ms aktualisiert, einen Punkt hinzufügen und entfernen. In Pseudocode:

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 
} 

Wenn 6-Serie in diesem Charting-Tool ausgeführt wurde, begann es ein wenig träge zu zeigen. Das Ändern des Ticks auf 10ms machte keinen Unterschied, das Diagramm wurde mit der gleichen Geschwindigkeit aktualisiert, so dass es scheint, dass 20ms das Tempolimit ist (UI oder Diagramm?).

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

Dann habe ich versehentlich beide aktiviert und 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 generieren, aber keine Daten beschädigt oder Speicherlecks.

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

Danke!


Edit: I einen Hintergrund der eingebetteten Codierung aufweisen, so dass, wenn I der Threads und Timings denke, ich glaube, sofort einen Stift in der Hardware von Makeln und Haken einen Umfang von bis zu 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?

Antwort

4

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

Das Ergebnis dessen, was Sie tun, indem Sie drei Timer hinzufügen, ist nur die "add data" -Methode dreimal pro Ereignisschleife statt einmal zu schießen, so wird es aussehen wie Ihre Diagramme gehen viel schneller, aber in der Tat Bildrate ist ungefähr 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 Wert, aber in Visiblox gibt es ein bisschen Magie, um die Auswirkungen zu mildern. Wenn Sie also Daten mit sehr hoher Geschwindigkeit hinzufügen, werden die Diagrammaktualisierungen zusammengeführt Die Rate der Render-Schleife und unnötige erneute Renders werden gelöscht.

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, z. B. indem Sie sie 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.

+0

Sie haben Recht. Es ist eine Illusion. Bei langsamen Geschwindigkeiten ist das "Rollen" des Charts leichter mit den Augen zu verfolgen und die Schwergängigkeit ist leicht zu erkennen, aber wenn man mehr als einen Punkt nach dem anderen aktualisiert, ist die Trägheit schwer zu erkennen. Vielen Dank! – PaulG

7

Ein DispatcherTimer, der sein Tick-Ereignis auf dem 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 mit irgendetwas (Verarbeitung der Eingabe, Aktualisieren des Diagramms usw.) beschäftigt ist, werden die Ereignisse des Timers verzögert. Darüber hinaus wird das Reaktionsverhalten Ihrer Anwendung verlangsamt, wenn Sie mehrere DispatcherTimers in sehr kurzen Intervallen auf dem Benutzeroberflächen-Thread absetzen, da das Tick-Ereignis ausgelöst wird und die Anwendung nicht auf Eingaben reagieren kann.

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 in einem Hintergrund-Thread gleichzeitig geändert wird, während der Vordergrund-Thread versucht, daraus zu lesen, stürzen Sie (wenn Sie Glück haben) oder sehen beschädigte Daten.

In Ihrem Beispiel haben Sie einen Kommentar, der besagt, dass "kein Diagramm aktualisiert werden muss, 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 eine 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 Dinge verlangsamen könnte.

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

In jedem Fall ist das Problem ein wenig schwierig but not completely new. Es gibt Möglichkeiten, wie Sie eine Sammlung in einem Hintergrund-Thread verwalten und über den UI-Thread an diese binden können. Aber je schneller Ihre Benutzeroberfläche aktualisiert wird, desto eher warten Sie auf einen Hintergrund-Thread, um eine Sperre aufzuheben.

Eine Möglichkeit, dies zu minimieren, wäre die Verwendung eines 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 UI-Thread 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 davon mit Dispatcher.BeginInvoke an den UI-Thread zu senden, wo Sie dann eine Sammlung sicher aktualisieren könnten.

+0

Danke für die tolle Einsicht. Um einige Ihrer Bedenken zu beantworten, verwende ich nicht genau eine List <>, sondern eine Implementierung einer Sammlung, für die INotifyCollectionChanged ist. Da dies eine Drittanbieter-App ist, kann ich nicht viel dagegen tun. Mein erster Ansatz war genau so, wie Sie es vorgeschlagen hatten. Ich habe eine Bitmap im Hintergrund gerendert, während ich eine Bitmap im Vordergrund scrollte. Aber aufgrund der zeitlichen Einschränkungen und der Qualität meines Charts, habe ich mich für ein Drittanbieter-Tool entschieden. Also LinkedList und andere sind out, ich muss ihre spezifische Implementierung verwenden ... – PaulG

+0

(Fortsetzung, lief aus Chars) Eine andere Sache, ich habe gerade festgestellt, dass System.Windows.Threading erstellt Threads, die im selben Thread wie die Benutzeroberfläche laufen, Andernfalls wäre es nicht möglich, die Benutzeroberfläche zu aktualisieren. Recht? Die Tatsache, dass ich dachte, ich wäre Multithreading, sollte falsch sein. Was ist dann los? Weist silverlight Zeitscheiben für die Freigabe zwischen UI und DispatchTimer zu und die Tatsache, dass ich mehrere davon erstellt habe, erhöht die Priorität der Timer-Aufgaben? – PaulG

+0

(Fortsetzung III) Dies ist auch der Grund, warum ich keine Abstürze oder Bugs sehe, weil ich eigentlich nicht mehrere Threads hintereinander laufe, sondern nur nacheinander.Meine Anwendung ist ein Simulationstool, das Tausende von Matrixberechnungen pro Zeitperiode verarbeitet, was zu Millionen von Berechnungen pro Sekunde führt. Ich hätte jetzt abstürzen sollen. Bitte korrigieren Sie mich, wenn ich falsch liege. Dies sind alles Annahmen. – PaulG