Laden...

[erledigt] WPF-Aktualisierung und BackgroundWorker

Erstellt von m.grauber vor 13 Jahren Letzter Beitrag vor 13 Jahren 3.538 Views
M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren
[erledigt] WPF-Aktualisierung und BackgroundWorker

Oh, wie ich dieses Thema hasse 🙄:

Direkt nach dem Öffnen eines WPF-Windows möchte ich einen eher kurzen Code durchlaufen lassen und im Fenster soll u. a. ein Label und Progressbar aktualisiert werden.

Natürlich wird es nicht korrekt gezeichnet: Hierzu gibt es tausende Workarounds die alle nicht funktionieren und als ein Ausweg wird der BackgroundWorker genannt.

Mein Problem dabei ist aber:

  • Der BackgroundWorker arbeitet zu langsam. Die Aktualisierung erfolgt nur in längeren Schleifen mit viel Rechenaufwand bzw. langer Laufzeit korrekt. Hat man nur weniger Aktionen, "verschluckt" er einige Ausgaben oder zeigt sie später an.

  • Der BackgroundWorker kann in einem ungünstigen Fall zwischen den Threads umschalten. Dann stimmen kurzfristig die Informationen (z. B. 3 Labels nicht mehr überein), da z. B. genau 2 Labels noch nicht aktualisiert wurden.

  • Im Debugger springen mir immer andere Aktionen dazwischen

  • Zudem muss ich ohnehin auf das Ende des Workers warten.

  • Schlimmstes Problem: im "ProgressChanged" kann ich zwar UI-Elemente aktualisieren, jedoch werden in meinem Hauptcode des Workers z. B. auch Observable Collections gefüllt. DataContexte auf DataGrids (sind natürlich auch UI-Elemente) können dort auch nicht zugewiesen werden.
    Ich kann den DataContext zwar am Worker-Ende zuweisen, es ist jedoch wieder ein Mehraufwand und bringt den Code durcheinander.
    Zudem kann ich im ProgressChanged nicht mehr Application.Current.Shutdown aufrufen, da es ja ein anderer Thread ist.

Ziemlich viel Aufwand, nur um einen kurzen Balken und Fortschritt anzuzeigen.

Vielleicht hat jemand einen Rat?

Danke

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

6.862 Beiträge seit 2003
vor 13 Jahren

Hallo,

die Grundprobleme und Lösungen sind in WPF ja die gleichen wie in Windows Forms, siehe auch [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

  • Der BackgroundWorker arbeitet zu langsam. Die Aktualisierung erfolgt nur in längeren Schleifen mit viel Rechenaufwand bzw. langer Laufzeit korrekt. Hat man nur weniger Aktionen, "verschluckt" er einige Ausgaben oder zeigt sie später an.

Der Backgroundworker arbeitet gar nicht, der Code den du für das DoWork Delegate angibst, führt die Arbeit aus - wenn was langsam läuft etc. liegts an dem Code, nicht an dem BackgroundWorker. Die Stellen an denen ReportProgress aufgerufen wird, müssen natürlich geschickt gewählt werden. Wenn du aber nur z.b. 3 Anweisungen hast die nicht feiner unterteilt werden können, dann kannst du auch nur 3 mal zwischen denen ReportProgress aufrufen - dann geht es einfach nicht genauer.

  • Der BackgroundWorker kann in einem ungünstigen Fall zwischen den Threads umschalten. Dann stimmen kurzfristig die Informationen (z. B. 3 Labels nicht mehr überein), da z. B. genau 2 Labels noch nicht aktualisiert wurden.

Ein BackgroundWorker hat nur ein Arbeitsthread. Da du sagst umschalten zwischen Threads, nehm ich an du hast mehrere BackgroundWorker. Dann ist es wieder Aufgabe für dich für die richtige Synchronisation der Threads zu sorgen, sowas kann der BackgroundWorker nicht automatisch machen. Ich würde dann aber eher zur Benutzung von normalen Threads raten - Threadsynchronisation ist an sich schon nicht trivial und da brauch man nicht noch zusätzlichen Code der einen da rumärgern kann.

  • Im Debugger springen mir immer andere Aktionen dazwischen Normal bei Multithreaded Programmen - der Debugger hält nur einen Thread an, die anderen laufen normal weiter wenn nicht explizit alle gestoppt werden.
  • Schlimmstes Problem: im "ProgressChanged" kann ich zwar UI-Elemente aktualisieren, jedoch werden in meinem Hauptcode des Workers z. B. auch Observable Collections gefüllt. DataContexte auf DataGrids (sind natürlich auch UI-Elemente) können dort auch nicht zugewiesen werden.
    Ich kann den DataContext zwar am Worker-Ende zuweisen, es ist jedoch wieder ein Mehraufwand und bringt den Code durcheinander.

Wenn was durcheinander kommt ist es nen Programmierfehler, das hat nichts mit dem BackgroundWorker zu tun. Du hast wie gesagt die Möglichkeit am Ende den DataContext auf einmal zu setzen, da würde sich ja RunWorkerCompleted anbieten. Oder aber du setzt es vor Beginn des BackgroundWorkers und füllst dann nur die Liste vom Backgroundworker aus. Die GUi aktualisiert sich dann ja automatisch durch die Change Notification bei der ObservableCollection.

Zudem kann ich im ProgressChanged nicht mehr Application.Current.Shutdown aufrufen, da es ja ein anderer Thread ist. Lass doch im Fehlerfall die DoWork Methode einfach normal enden und setz nen Flag welches vom RunWorkerCompleted Delegate dann ausgewertet werden kann und in dem du dann bei Bedarf die Applikation beenden kannst.

Baka wa shinanakya naoranai.

Mein XING Profil.

5.742 Beiträge seit 2007
vor 13 Jahren

Hallo m.grauber,

häufig ist bei der Anzeige eines Fortschrittes auch ein Timer (im speziellen Fall ein DispatcherTimer) hilfreich: Statt den View stetig mitzuteilen, dass sich der Fortschritt geändert hat (was schnell zum Einfrieren führen kann), pollt man mit dem Timer einfach eine Property der Klasse, die den Task ausführt. Ein Intervall von 100-250ms sollte hierbei ausreichen.

So schafft man schon mal ein paar Probleme mit der Synchronisation und den zu häufigen Aktualisierungen aus der Welt.

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo talla,

vielen Dank für die ausführliche Hilfe! 👍

ja, den sehr guten Betrag hatte ich u. a. auch bereits gelesen.

Hat man nur weniger Aktionen, "verschluckt" er einige Ausgaben oder zeigt sie später an.

--> Ja, ich möchte natürlich nur die von mir angegebenen Aktualisierungen sehen und nicht mehr. Ich meinte ein etwas seltsames Verhalten im Debugger: Obwohl der Code für die Ausgabe bereits durchlaufen wurde und z. B. ein Label aktualisiert wurde, arbeitet er im "Hauptcode" bereits wieder weiter und nach 3-4 Schritte in diesem Hauptcode, führt er auf einmal die Bildschirmaktualisierung des Labels etc. durch.
Im Debugger stört mich das seltsame Verhalten nicht weiter, wenn jedoch im Echtbetrieb beim Kunden ein ähnliches Verhalten erfolgt, könnte ich im Fehlerfall durch ein noch nicht aktualisiertes Label die auf falsche zugehörige Aktion schließen, bei dem der Fehler auftritt. Ich hoffe also, es liegt nur am Debugger.
Aber scheinbar will WPF unbedingt selbst entscheiden, wann ein Control neu gezeichnet werden soll.

Da du sagst umschalten zwischen Threads, nehm ich an du hast mehrere BackgroundWorker.

--> Entschuldige meine zweideutige Ausdrucksweise: Ich meinte mit Threads den normalen "Programmthread" und den einen "Backgroundthread". Mit mehreren Background-Workern zu arbeiten, möchte ich wirklich vermeiden 😜 ! Ich meinte folgendes:
Wenn ich bei der Bildschirmaktualisierung z. B. 3 Labels habe: "Datenbank:", "Tabelle:", "Aktion:" muss ich die Informationen in 3 Codezeilen nacheinander aktualisieren. Es kann vorkommen, dass der Thread genau nach der 1. Zeile wechselt und die folgenden beiden Zeilen erst einige Sekunden später aktualisiert werden, wenn der Thread die Zeitscheibe wiedererlangt. Damit sieht der Anwender evtl. falsche zusammengehörige Informationen angezeigt. Nur wie kann man verhindern, dass der Worker an einer bestimmten Zeile ersteinmal zum Hauptthread kurzzeitig umschaltet?

Lass doch im Fehlerfall die DoWork Methode einfach normal enden und setz nen Flag welches vom RunWorkerCompleted Delegate dann ausgewertet werden kann und in dem du dann bei Bedarf die Applikation beenden kannst.

--> Danke für den Tipp! So etwas wollte ich eigentlich vermeiden, aber dann werde ich es wohl so machen.

Vielen Dank und Grüße!

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

U
1.688 Beiträge seit 2007
vor 13 Jahren

Hallo,

Aber scheinbar will WPF unbedingt selbst entscheiden, wann ein Control neu gezeichnet werden soll.

WPF rendert die Oberfläche tatsächlich asynchron. Aber das stellt i. allg. kein Problem dar.

Es kann vorkommen, dass der Thread genau nach der 1. Zeile wechselt und die folgenden beiden Zeilen erst einige Sekunden später aktualisiert werden, wenn der Thread die Zeitscheibe wiedererlangt.

Wenn es wirklich "einige Sekunden" sind, läuft etwas falsch. Zeig' doch mal ein bisschen Code.

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo Winsharp!

danke auch Dir für diesen Tipp! 👍

Ich habe es mir angesehen und mir gefällt, dass der Code etwas reduziert wird. =) Das Einsatzgebiet ist etwas anders und ich werde dies an einer weiteren Stelle bei mir einsetzen, wo der Code noch "etwas gleichfoermiger" ist.

Vielen Dank!

Grüße

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo ujr,

ok, dann muss ich das wohl so akzeptieren.

Einige Sekunden ist vielleicht etwas übertrieben und trifft nur im Debugger zu. Wenn jedoch ein Fehler auftritt, bleibt der falsche Stand stehen und vermittelt einen falsche Information.

Code an einem Stück z. B.:


LabelDatenbank.Content="Datenbank2";
//Hier stoppt z. B. C# den Thread und schaltet ersteinmal zum Hauptthread um. 
//Der Anwender sieht also kürzere Zeit "Datenbank2" und "Tabelle1" (von der 
//letzten Aktualisierung), obwohl bereits Tabelle2 gemeint ist.
//Erst nachdem wieder zu diesem Thread umgeschalten wird, wird folgendes 
//ergänzt:
LabelTabelle.Content="Tabelle2";
LabelAktion.Content="Prüfen";

Grüße

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

U
1.688 Beiträge seit 2007
vor 13 Jahren

Hallo,

Code an einem Stück z. B.:

mich hätte mehr das "drumherum" interessiert. In welchem Thread läuft das, bspw.

//Hier stoppt z. B. C# den Thread und schaltet ersteinmal zum Hauptthread um.

Wie kommst Du darauf?

Und was heißt

Wenn jedoch ein Fehler auftritt, bleibt der falsche Stand stehen und vermittelt einen falsche Information.

5.299 Beiträge seit 2008
vor 13 Jahren
  
LabelDatenbank.Content="Datenbank2";  
//Hier stoppt z. B. C# den Thread und schaltet ersteinmal zum Hauptthread um.   
//Der Anwender sieht also kürzere Zeit "Datenbank2" und "Tabelle1" (von der   
//letzten Aktualisierung), obwohl bereits Tabelle2 gemeint ist.  
//Erst nachdem wieder zu diesem Thread umgeschalten wird, wird folgendes   
//ergänzt:  
LabelTabelle.Content="Tabelle2";  
LabelAktion.Content="Prüfen";  
  

Die 3 Zeilen sollten in nicht merkbarer Zeit durchlaufen sein, egal ob ein nebenläufiger Thread etwas macht oder nicht.
Wenn es eine Verzögerung gibt, kann es vlt. sein, dass Teile des nebenläufigen Codes vlt. zurück-delegiert werden, und nun doch im selben Thread läuft wie dieses hier.
(Jedenfalls bei WinForm-Threading kann man sich u.U. auf diese Weise selbst hereinlegen 😉 )

Das mit dem Fehler verstehe ich auch nicht - wenn es einen Fehler gibt, mußt du eh explizit reagieren, und kannst die Anzeige so nicht stehen lassen.
(Am besten natürlich, du programmierst so, dass kein Fehler auftritt.)

Der frühe Apfel fängt den Wurm.

6.862 Beiträge seit 2003
vor 13 Jahren

Mit was für ner Priorität invokst du denn das aktualisieren der GUI? Wie urj schon sagte, das drumherum ist hier wichtiger, die drei Zeilen machen nichts wichtiges.

Baka wa shinanakya naoranai.

Mein XING Profil.

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo ujr, Erfinder des Rades, talla,

war leider bei einem Kunden und komme erst jetzt wieder dazu. Danke für alle Infos!

@ujr: "Wie kommst Du darauf?" --> weil ich das im Debugger schrittweise nachvollziehen kann. Aber das Problem ist erstmal gelöst:

@talla: Super: Die Priority war auf normal. Habe ich auf höher gestellt und nun aktualisiert er sofort! 👍 Das Problem ist ersteinmal gelöst!

@Erfinder des Rades:

...wenn es einen Fehler gibt, mußt du eh explizit reagieren, und kannst die Anzeige so nicht stehen lassen.
(Am besten natürlich, du programmierst so, dass kein Fehler auftritt.)

--> ja, ich versuch mein Bestes =) aber ich merke schon: es ist im separatem Thread alles etwas aufwändiger. 🙁

Vielen Dank nochmals an Alle! 👍 👍 👍 👍 👍

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]