Laden...

Eventsystematik mit GUI-Aktualisierung über mehrere DLLs verlangsamt Prozess drastisch

Erstellt von sharpType vor 13 Jahren Letzter Beitrag vor 13 Jahren 5.290 Views
S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren
Eventsystematik mit GUI-Aktualisierung über mehrere DLLs verlangsamt Prozess drastisch

Hallo Profis,

ich habe mal eine prinzipielle Frage und mich verwundert langsam einiges und ich bin mir nicht sicher, ob es für mein Problem nicht eine bessere Lösung gibt. Folgendes Szenario:

Ich habe ein Hauptprogramm A, welches praktisch nur aus der GUI besteht. Von dem Programm steuer ich eine Klassenbib B an in der Kernalgorithmen in Threads laufen, die von der Bedienung vom User in A gestartet werden. Dieser Kern B verbindet jedoch weitere Klassenbibs C, D, E ..., weil dort weitere Algos laufen, die nichts in B verloren haben und so thematisch in einer der weiteren Klassenbibs (C, D, E ..) ausgegliedert wurden.

Das ganze hat den Vorteil, dass alles sehr geordnet ist und nur Algos in Libraries aufzufinden sind, die thematisch auch da reingehören. Diese lassen sich so gut projektübergreifend verwenden, ohne, dass man alles was ich für Projekt X benötige aus Projekt Y herauskopieren muss und dann an zwei Stellen den gleichen Code habe. So ist alles schön zentral.

Jetzt das Problem: Wenn ich in A ein Thread starte, der erstmal in B läuft, damit der GUI-Thread in A nicht blockiert wird, dann feuere ich mit Events aus B den Fortschritt und zeige diesen in A an. Damit ich kein threadübergreifenden Vorgang bekomme führe ich mit Delegaten und entsprechenden Invokes dann die Aktualisierungsmethode für den Fortschrittsbalken in A aus.

Zwischenfrage 1: Gibt es dafür eine andere, vielleicht bessere Methode als über Events und Delegaten?

Es geht weiter. Wenn ich viele Threads in B habe, dann schlage ich mich demnach mit vielen Events herum, die in A registriert werden müssen, das ist schon sehr unübersichtlich. Das eigentliche Problem kommt aber jetzt erst. Wenn der laufende Thread in B nun weitere Algos ausführt, die sich wiederrum in C, D und E befinden und diese ebenfalls lange dauern, d.h. einen Fortschritt zurückgeben sollen, dann leite ich die Events, die dann in C, D, E gefeuert werden, um den Fortschritt zu signalisieren, an die übergeordnete Library B weiter, wo dann anschließend die nächsten Events gefeuert werden, um von B den Fortschritt dann nach A zu leiten und in A über Delegaten und Invokes den eigentlich Fortschritt im Balken anzeigen.

D.h. ich registriere in A Fortschrittsevents für B und in B Fortschrittsevents für C und in C im schlimmsten Fall nochmal Fortschrittsevents für D. Je tiefer die Verschachtelung wird, desto länger und komplizierter der Weg.

Mir ist aufgefallen und das ist echt heftig, dass wenn ich z. B. eine sehr große Datei einlese und mit den Daten Operationen durchführe, es teilweise mit Fortschrittsbalken (demnach mit der ganzen verschachtelten Eventsystemaik) 10 Sekunden dauert und ohne den ganzen Fortschritt nur 1 Sekunde.

Kernfrage: Wie löst man so ein Problem und wie macht man es richtig und/oder gibt es Alternativen und wenn ja welche?

Ich bin total am verzweifeln, da ich ein Fan von Struktur bin und ungern alle Algorithmen in ein Projekt stopfe, die vielleicht auch projektübergreifend in anderen Projekten genutzt werden sollen und trotzdem ein Fortschritt signalisieren sollen und weiterhin auch noch in Threads laufen müssen.

Ich hab mir viel Mühe beim Schreiben dieses Posts gegeben, um nichts zu verwechseln und ich hoffe, dass man es einigermaßen verstehen kann =)

Ich bedanke mich schon mal!

Gruß

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

Ich hab mir viel Mühe beim Schreiben dieses Posts gegeben, um nichts zu verwechseln und ich hoffe, dass man es einigermaßen verstehen kann =)

Ich habs gut verstanden.

Gibt es dafür eine andere, vielleicht bessere Methode als über Events und Delegaten?

Allgemein ist das ist ein Observer, der wird aber in C# mit Events am elegantesten umgesetzt. Anders ist nicht möglich keine direkten Abhängigkeiten zu haben. Also das vorgehen ist so in Ordnung.

Wie löst man so ein Problem und wie macht man es richtig und/oder gibt es Alternativen und wenn ja welche?

Bedenke wenn A eine Thread für B startet und B einen Thread für C startet usw. dass es dann zuviele Threads werden können -> pro CPU kann nur ein Thread zu einem Zeitpunkt arbeiten -> Oversubscription. Generell ist meist am besten den Thread so weit "oben" wie möglich zu erstellen. Hier also in A für B und C die synchron (innerhalb des Threads) laufen. Aber darum gehts dir in den Fragen nicht vordergründig.

Der reine Aufruf eine Delegaten/Events fällt nicht ins Gewicht. Entscheidender ist da schon was der Target/EventHandler zu erledigen hat. Wenn dies eine aufwändige Operation ist dann zieht sich das Ganze in die Länge wenn mit Control.Invoke gearbeitet wird. Bei Invoke erfolgt der Kontext-Wechsel der Threads sofort damit die Ausführung synchron erfolgt. Von daher wäre die Vewendung von Control.BeginInvoke besser. Dabei wird der Delegat in die Aufgabenqueue des UI-Threads gesteckt und dann ist es aufrufende Thread damit fertig. Die Ausführung erfolgt also asynchron.
Daher ist in [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) auch von CheapGuiAccess die Rede.

Das mit den "tiefen Verschachtelungen" musst du ein wenig anders sehen. A -> B und mehr weiß A nicht. B -> C und mehr weiß B nicht. Wenn also C ein Event feuert dann weiß nur B dass das Event von C stammt, A weiß davon (auch wenn es letzten Endes davon betroffen ist) überhaupt nix, nur dass von B ein Event kommt.
Bei diesem Vorgehen ist nichts schlechtes dabei.

Beachte auch dass für kurze Operationen ein Fortschrittsbalken nicht unbedingt verwendet werden muss. Eine "Busy Indicator" reicht da. Wenn die Operation lang genug ist würde ich auch auf eine Fortschrittsbalken setzen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo sharpType,

auch wenn gfoidl im Grunde schon alles gesagt hat, möchte ich es doch noch mehr auf den Punkt bringen:

Ich kann mir nicht vorstellen, dass die Events selbst oder die eigentliche Event-Behandlung die Laufzeit nennenswert verlängern.

Stattdessen vermute ich, dass Control.Invoke - was eine relativ teure Operation ist - zu häufig aufgerufen wird.

Sicher kannst du mal probieren, die billigere Operation Control.BeginInvoke zu verwenden, noch besser ist es aber die Frequenz zu reduzieren. Es bringt eh nichts, das GUI mehr als 10 mal pro Sekunde zu aktualisieren, also sollten auch nicht mehr als ca. 10 Delegationen ans GUI pro Sekunde erfolgen. Und wenn das erreicht ist, spielt es performacetechnisch kaum noch eine Rolle, ob man die Delegation mit Control.Invoke oder Control.BeginInvoke durchführt.

herbivore

Gelöschter Account
vor 13 Jahren

Weiters ist ein guter Ansatzpunkt einen Fortschritt immer nur Prozentweise zu melden. Wenn du z.B. 100.000 Teilschritte hast, dann ist es besser nur jedes einzelne fertige Prozent zu melden anstatt die 100.000 Events zu feuern. Damit reduzierst du die anzahl der Events erheblich.

D
91 Beiträge seit 2005
vor 13 Jahren

Hallo gfoidl!

Ich habe zwar nichts zum Thema beizutragen, möchte aber dennoch loswerden, dass ich deine Erklärung zu dem Thema sehr hilfreich fand.

VG, Florian

S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren

Hey,

ich habe zwar noch keine von euren Tips umgesetzt (das werde ich gleich tun), aber ich glaube das waren die entscheidenen Tips, die ich gebraucht habe, bin wirklich dankbar, denn folgendes:

Zuerst kurz: Trotz durchlesen meines Posts viel mir gerade noch etwas auf, was aber auch schon bemerkt wurde:

Bedenke wenn A eine Thread für B startet und B einen Thread für C startet usw. dass es dann zuviele Threads werden können -> pro CPU kann nur ein Thread zu einem Zeitpunkt arbeiten -> Oversubscription. Generell ist meist am besten den Thread so weit "oben" wie möglich zu erstellen. Hier also in A für B und C die synchron (innerhalb des Threads) laufen. Aber darum gehts dir in den Fragen nicht vordergründig.

Es wird nur ein Thread von A aus gestartet. Der Thread läuft dann und ist allein, ruft aber Methoden von B, C, D etc. auf! 🙂

Dann glaube ich das entscheidene ist, dass ich ein eigenen Fortschrittsbalken in einer DLL habe, der generell die Aktualisierungsmethode des Balkens in dieser mit Control.Invoke aufruft, wenn InvokeRequired = true ist und das auch (damals noch zur Sicherheit, weil ich in A kein Delegaten hatte, der vom Thread ins GUI ueberträgt) obwohl ich derzeit schon threadsicher in A bin. Das heißt eigentlich doppelt gemoppelt? Aber da ich InvokeRequired abfrage theoretisch doch nicht doppelt gemoppelt. Ich werds ausprobieren. Vielleicht liegt es aber auch, wie beschrieben, daran, dass ich tatsächlich bei jedem Schleifendurchlauf die Fortschrittsevents feuere und mich nicht darum kümmere, wie oft das eigentlich passiert.

In A, wenn ich in den Fortschrittseventhandler des Threads gelange, arbeite ich mit


this.invoke(new Delegates.DELEGATX(GUI_Fortschrittanzeigmethode())

um von dem Thread in den GUI-Thread zu gelangen, der dann das GUI bearbeiten darf. Oder sollte hier auch mit BeginInvoke gearbeitet werden?

Was mir neben dieser Prozessverlängerung noch aufgefallen ist, ist in dem Fortschrittsbalken die Aktualisierung des Labels, die in derselben Methode durchgeführt wird, die auch den Balkenfortschritt ändert (logisch). Das Label hängt total hinterher. Der Balken ist schon bei ca. 80% und das Label zieht so langsam nach mit der Anzeige. Das sieht wirklich lustig aus, aber es ist nicht sehr schön und ich vermute mal, dass das mit der ganzen Geschichte zusammenhängt.

Ich werde die Tips mal berücksichtigen und mal schauen, was dabei rauskommt.

Eine generelle Frage dazu hätte ich aber noch:

Ich definiere meistens nur ein Event z. B. ReportProgress in z. B. Library C. Eine Methode in C, die einen Fortschritt zeigen soll, sieht dann ungefähr so aus:


public void Method_A(object x)
{
   OnReportProgress(0);
   for(......)
   {
      ...
      OnReportProgress(i*100/gesamt);
   } 
   OnProgressFinished(resultObject);
}

Jetzt registriere ich natürlich nicht für jede Methode in C, die einen Fortschritt zeigen soll, ein neues Event. Dann hätte ich ja hunderte Events. Ich nehme stattdessen immer dassselbe.

Ist es richtig, dass die Konsequenz dazu ist, dass ich in A, B oder wo auch immer das Event registriert wurde und die "Behandlung" stattfinden, ich das Event an genau dieser stelle unmittelbar wieder deregistrieren muss, da sonst der Eventstapel aufgestockt wird und die Events nacheinander abgearbeitet werden und ich so evtl. Probleme bekommen kann?

Es wäre nämlich echt unkomfortabel, wenn ich in den Libraries fuer jede Methode, die einen Fortschritt zeigen soll, einen extra Fortschrittsevent definieren muss.

Was sagt ihr dazu? 🤔

Ich bedanke mich erneut!

Gruß

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

dass ich ein eigenen Fortschrittsbalken in einer DLL habe

Kann sein dass ich das jetzt falsch verstanden habe, aber die Arbeiter-Klassen sollten vom Fortschrittsbalken nix wissen. Nur die UI (also die Form) soll das wissen und auch nur diese weiß dass auf sie nur der eigene Thread zugreifen darf. Also erledige das Control.Invoke in der UI und sonst nirgends.

Jetzt registriere ich natürlich nicht für jede Methode in C, die einen Fortschritt zeigen soll, ein neues Event

Registriert wird ein Handler bei einem Event. Hier meinst du wohl Deklarieren?!
Hier auch

dass ich in A, B oder wo auch immer das Event registriert wurde und die "Behandlung" stattfinden

...wo auch immer der Event-Handler registriert wurde...
Das sind kleine Unterschiede deren Bedeutung aber ganz anders ist.

Aber zur Frage: Wenn im ProgressFinished-Ereignis der Handler wieder deregistriert wird ist es gut. Da ein Event ein MulticastDelegate ist würde sonst mit jedem Neuregistrieren eines Handlers die "Aufrufkette" halt entsprechend lang werden.
Wenn es aber wie in deinem Beispiel eine Method_A und eine Method_B die nicht gleichzeitig arbeiten und die Handler immer dasselbe machen kannst du das Event von C in A auch registiert lassen - vorausgesetzt du registrierst es nur 1x.
Vergleiche: Ein Button-Klick wird auch nur 1x zu Beginn registriert und geht problemlos 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

4.221 Beiträge seit 2005
vor 13 Jahren

Weiters ist ein guter Ansatzpunkt einen Fortschritt immer nur Prozentweise zu melden. Wenn du z.B. 100.000 Teilschritte hast, dann ist es besser nur jedes einzelne fertige Prozent zu melden anstatt die 100.000 Events zu feuern. Damit reduzierst du die anzahl der Events erheblich.

Genau. Vorallem die 100.000 Invokes in den UI-Thread.

Habe gerade keine IDE auf diesem Rechner um es im zu testen... aber man könnte sich eine eigene Progressbar erstellen (ableiten von ProgressBar) mit eigenen Methoden, welche man aus einem x-beliebigen Thread aufrufen kann. Innerhalb der Methode prüfen ob sich ein Update lohnt (dann per BeginInvoke UI uppdaten) anderenfalls einfach auf den Update verzichten (da die Grössenänderung eh minimal wäre).... den neuen Wert und den aktuell angezeigten Wert müsste man natürlich bei jedem Call wieder neu setzen.

Mit diesem Ansatz hat man zwar immer noch 100'000 Aufrufe, reduziert aber die UI-Calls drastisch (z.B: auf maximal 100 bei prozentualem Update).

Nachtrag: zusätzlich kapselt man so natürlich das Verhalten der Progressbar (wann soll sie sich updaten).

Gruss
Programmierhans

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren

Kann sein dass ich das jetzt falsch verstanden habe, aber die Arbeiter-Klassen sollten vom Fortschrittsbalken nix wissen. Nur die UI (also die Form) soll das wissen und auch nur diese weiß dass auf sie nur der eigene Thread zugreifen darf. Also erledige das Control.Invoke in der UI und sonst nirgends.

Ja das ist klar, ich habe nur einen separaten Fortschrittsbalken der projektübergreifend genutzt wird und den nur A kennt. Praktisch das was Programmierhans beschrieben hat, nur bisher ohne diese schlaue Abfrage, ob sich ein Update lohnt.

Registriert wird ein Handler bei einem Event. Hier meinst du wohl Deklarieren?!

Korrekt! Manchmal fällt es mir einfach nicht ein!

Wenn es aber wie in deinem Beispiel eine Method_A und eine Method_B die nicht gleichzeitig arbeiten und die Handler immer dasselbe machen kannst du das Event von C in A auch registiert lassen - vorausgesetzt du registrierst es nur 1x.
Vergleiche: Ein Button-Klick wird auch nur 1x zu Beginn registriert und geht problemlos 😉

Macht Sinn, dann hab ich das wohl verstanden. Aber der Knackpunkt ist wirklich das mit dem "nicht gleichzeitig" arbeiten, weil sonst gibt es logischerweise Probleme.

Mit diesem Ansatz hat man zwar immer noch 100'000 Aufrufe, reduziert aber die UI-Calls drastisch (z.B: auf maximal 100 bei prozentualem Update).

Genau ich hätte immernoch 100'000 Event-Feuerungen! Wäre es dann nicht besser in dem Thread zu prüfen und nur dann zu feuern, wenn sich ein Update lohnt? Wenn jedoch wirklich nur Invoke den Prozess so verlangsamt und nicht die Auslösung von den 100'000 Events, dann wäre es wohl besser das zentral in der Fortschrittsbalken-DLL (in meinem Fall) zu prüfen.

---> Würdet ihr nur den Wert überprüfen, ob der sich gegenüber dem Vorgängerwert um 1 erhöht hat (angenommen 0-100 %) oder kann man das geschickter machen?

Vielen Dank

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

Würdet ihr nur den Wert überprüfen, ob der sich gegenüber dem Vorgängerwert um 1 erhöht hat (angenommen 0-100 %) oder kann man das geschickter machen?

Du könntest dir eine Hilfsklasse schreibe welche die Berechnung durchführt und dann ein Event feuert 😁

Nein, im Ernst: Wenn du von ProgressBar ableitest wie Programmierhans vorgeschlagen hat kannst du dort die Maximum-Eigenschaft auf das absolute Maximum setzen, die Minimum-Eigenschaft hat den Default-Wert von 0. Zusätzlich implementierst du eine


public void ReportProgress(int absolutValue)
{
    int value = (100 * absolutValue) / this.Maximum;
    this.Value = value;
}

Methode. Darin berechnest du über Ganzzahldivision* den relativen Wert und setzt die Value-Eigenschaft. Ob sich der Wert in Hinsicht Aktualisierung des Balkens geändert hat prüft der Setter der Value-Eigenschaft (hab mit dem Reflector geguckt). Diese Arbeit wird dir also schon abgenommen.

* deshalb zuerst mit 100 multiplizieren sonst käme immer 0 raus 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren

Hallo,

ich habe in meiner Fortschritts-DLL, nenn ich sie mal so, praktisch nur eine Form, die drei normale ProgressBars enthält, also ich habe keine spezielle ProgressBar, nur eben eine spezielle Form mit drei normalen Bars.

Ich habe dort jetzt einfach die Änderung abgefragt und zwar wenn NeuerWert - AlterWert ≥ 1 ist, dann kann er eine Bar aktualisieren, sonst eben nicht. Das jedoch bei prozentualem Fortschritt von 0 bis 100.

Jetzt habe ich mal alle Tips ausprobiert und echt abgefahrene Erkenntnisse gemacht. Ersteinmal habe ich in A nicht mehr mit Invoke sondern mit BeginInvoke und den Delegaten gearbeitet, dann geht alles ratz fatz und ohne Verlangsamung (die Abfrage bei den ProgressBars bereits implementiert). Jetzt fällt mir natürlich auf, das die Operationen, wo ich dachte sie brauchen lange (wohlbemerkt DACHTE), in bruchteilen einer Sekunde ablaufen. Dort jedoch werden Events wie ChangeProgressBarText gefeuert, die natürlich, da ich BeginInvoke nun nutze, gar nicht mehr sichtbar werden auf der Form, weil alles so schnell abläuft, was ja wiederrum gut ist. Ergo habe ich wohl ein Fehler gemacht und zwar ein Fortschrittsbalken wo eingebaut, wo eigentlich gar keiner hingemusst hätte?!

Möchte ich jedoch (also praktisch eine mutwillige Verlangsamung einbauen) dass die Teilschritte der eigentlich nicht nennenswerten Operationen angezeigt werden, soltle ich mit Invoke arbeiten und die Verlangsamung aber dafür die korrekte Fortschrittsdarstellung in Kauf nehmen?!

Weitere Erkenntnis. Ich lese in einem Thread eine Datei ein, die hat ca. 100.000 Zeilen. Aus jeder Zeile erstelle ich aus vorherigen Abfragen bestimmte Objekte und speichere diese ab. Wenn ich nun nach jeder Zeile ein Event feuere, welches mir den Einlesefortschritt berichten soll, dann:

  • verlangsamt diese Feuerung des Events die Fortschrittsanzeige ERHEBLICH, wenn ich SOWOHL ALS AUCH mit invoke oder beginInvoke in A arbeite

Wenn ich das Event da komplett weglasse, geht die Einlesung binnen einem bruchteil einer Sekunde. Also denke ich mal habe ich auch hier den PC mal wieder unterschätzt und irgendwo ein Fortschittsbalken eingebaut, wo eigentlich keiner hingehört?!

Jetzt aber nochmal zu der Erkenntnis davor, nämlich der, dass wenn ich mit Invoke arbeite er die einzelnen Fortschritte, die binnen bruchteile einer Sekunde ablaufen könnten, sauber im Balken darstellt und wenn ich mit BeginInvoke arbeite er die praktisch einfach überspringt und die GUI darauf gar nicht reagiert, weil es warscheinlich asynchron abläuft, wie die Methode es ja beschreibt und somit alles für ihn viel zu schnell geht:

Würde das praktisch bedeuten, dass wenn:

  1. Ich keine Events in Methoden feuere, die eigentlich superschnell ablaufen können, ich keinen Fortschrittsbalken habe, aber wenn ich dann im Vergleich zum Testdatenumfang einen um ein Vielfaches höheren Datenumfang habe der User praktisch denken könnten, dass der PC nichts macht, weil er keinen Fortschritt sieht? (Notfall wäre wohl eine Sanduhr o. Ä. darzustellen)

  2. Ich die Einzelevents feuere in den Methoden, die eigentlich superschnell ablaufen, mit INVOKE arbetie und eine quasi Verlangsamung in Kauf nehme und mich aber absichere, so dass der User zu JEDEM Zeitpuntk genau weiß was der PC in welcher Methode macht und das sowohl bei Testdatenumfang und bei Megadatenumfängen

  3. Die vielleicht beste Lösung dafür: Einzelevents in Methoden zu feuern, die superschnell ablaufen könnten, jedoch mit BEGININVOKE arbeite und somit für den Fall dass der Datenumfang klein ist, der User nicht sieht was passiert, weil es zu schnell passiert, der PC aber fix damit fertig ist und FALLS mal Megadatenumfänge existieren der User alle Fortschritte sieht, weil die Operationen so lange dauert, dass BeginInvoke genug Zeit hat die GUI zu aktualisieren? (Habe das mal getestet mit Thread.Sleep und ich meine, das würde funktionieren)

Oman alles total kompliziert, aber ich muss das jetzt verstehen!!!! 🤔 =)

Hoffe ihr könnt halbwegs verstehen, was ich meine X(

Und sorry, wenn ich manche Sachverhalte vielleicht wieder nicht 100%ig korrekt in Fachwörter gefasst habe, musste zusehen, dass ich nicht wieder die Hälfte vergesse 🙂

Ich bedanke mich erneut!

Gruß

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo sharpType,

es ist gar nicht kompliziert. Es ist ganz einfach. Wie du selbst festgestellt hast, kostet das Feuern von Events kaum Zeit. Zeit kostet Control.Invoke/BeginInvoke und auch das nur, wenn es unnötig oft aufgerufen wird. Daraus ergibt sich automatisch alles weitere (außerdem wurde oben schon explizit gesagt, wie man beides unter einen Hut bekommt). Romane, wie du sie schreibst, sind daher vollkommen überflüssig.

herbivore

4.221 Beiträge seit 2005
vor 13 Jahren

Eine Möglichkeit habe ich ja schon beschrieben.

Es gibt natürlich auch noch andere Alternativen wie z.B:

Erstelle eine von Progressbar abgeleitete Klasse. Diese verwendet intern einen Timer welcher z.B: alle 200 ms tickt.

Dazu eine interne Variable für den aktuellen Value. Diese Variable kann von aussen per Property aus jedem Thread gesetzt werden (Variable kann als volatile angelegt werden)).

Der Timer liest sich zyklisch den Wert der Variable und schreibt diesen in den base.Value.

Auch bei einem Event-Geballer aktualisiert sich die Progressbar nur 5 mal pro Sekunde. Die Anzeige ist jeweils maximal 200 ms hinter der aktuellen Berechnung aktualisiert... müsste passen.

Dieses Vorgehen hat den Vorteil, dass der Setter der Variable NIE den aufrufenden Thread blockt (ich persönlich würde sogar auf eine Thread-Synch verzichten). Somit ist die ganze Spassbremse weg und die Progressbar bremst den Aufrufer nicht aus.

Programmierhans

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren

Ok, vielen Dank euch auf jeden Fall für die ganzen Hinweise, das hat mir sehr weitergeholfen. Ja ich gebe zu, ich neige dazu alles kompliziert darzustellen =)

Ich werde jetzt mal meine Progress-DLL mit dem Timer ausstatten und die SetProgressBarValue-Methde mit BeginInvoke statt Invoke threadsicher machen und durch den Timer dann nur maximal 5mal in der Sekunde aufrufen. Wobei bei prozentualem Fortschritt eine Abfrage auf ganzzahlige Wertänderung ja zusätzlich eingebaut werden kann.

Vielen Dank! 👍

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

Ja ich gebe zu, ich neige dazu alles kompliziert darzustellen

"und verwechsle gerne die Begriffe." hättest du noch ergänzen können 😉
Verwende doch Begriffe von denen du weißt was sie bedeuten oder umschreib sie so dass klar ist was du meinst. Beispiel:

mit BeginInvoke statt Invoke threadsicher machen

Threadsicher hat damit rein gar nix zu tun. Es geht darum dass die UI-Operation in den UI-Thread delegiert wird. Steht ausführlich in [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

Das mit dem Timer ist zwar eine schöne Idee finde ich aber nicht nötig wenn die Aktualisierung der ProgressBar nur dann stattfindet wenn sich der Prozentwert geändert hat (siehe oben). Das kannst du auch noch anpassen dass zB nur alle 5% eine Aktualisierung durchgeführt wird. Warum willst du also mit dem Timer das ganze noch verkomplizieren (wenn es dir so schon zu kompliziert ist wie du schreibst)?

Wobei bei prozentualem Fortschritt eine Abfrage auf ganzzahlige Wertänderung ja zusätzlich eingebaut werden kann.

Wozu wäre das in Kombination mit dem Timer sinnvoll? Denke darüber noch mal nach.

Eigentlich wurden eh schon mehr als genügend Anregungen gegeben. Lies dir die noch durch und denke über diese auch nach.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren

Ok. Es ist gerade noch ein Problem eingetreten bei der verwendung von BeginInvoke, wo ich keine Erklärung für habe.

Ich habe die Aktualisierung des Fortschrittsbalken jetzt mit der Methode BeginInvoke durchgeführt. Nun wird der mit Invoke() (für das Finish-Event habe ich Invoke benutzt, aber mit BeginInvoke tritt hier dasselbe Problem auf) in den GUI-Thread delegierte Eventhandler nicht mehr ausgeführt für das Finish-Event, welches gefeuert wird, wenn die Arbeitsmethode im Thread abgearbeitet ist. Das ist total komisch, der Fortschrittsbalken steht auf 100% und bleibt einfach sichtbar, da der GUI-Handler gar nicht mehr ausgeführt wird, der u.a. die Fortschrittsform mit den Progressbars schließt.

Arbeite ich jedoch mit Invoke() bei der Fortschrittsbalkenaktualisierung klappt alles wunderbar. Ich kann mir das nicht erklären. Ich delegiere nur mit Invoke() den Finish-Event-Handler vom Thread, wenn dieser abgearbeitet ist, in den GUI-Thread.

Der Finish-Event-Handler im Thread! wird auch aufgerufen (habe dort mal ein Haltepunkt gesetzt), jedoch wird der delegierte Finish-Event-GUI-Handler nie aufgerufen, wenn ich in der Fortschrittsform die Balken mit BeginInvoke aktualisiere.

Steht da vielleicht irgendwas in so eine "Warteschlange" oder so etwas?

Kann mir da nochmal jmd. einen Tipp geben? Bin langsam echt genervt von diesem Thema 😦

Dann nochmal eine Frage zu dem Thema allgemein: Wenn ich mit BeginInvoke arbeite in der Fortschrittsbalkenaktualisierung und das Label es nicht mal schafft zu reagieren, d.h. immer auf 0% bleibt oder abrupt auf 80-100% springt, kann man davon ausgehen, dass an dieser Stelle kein Fortschrittsbalken Sinn macht? Oder ist so ein "LAG" sozusagen die Konsequenz von BeginInvoke, die man zu tragen hat? Der Fortschritt verläuft zwar insgesamt viel schneller, aber wirklich sehr unsauber in der Darstellung. 😦

Vielen Dank.

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo sharpType,

Wenn ich mit BeginInvoke arbeite in der Fortschrittsbalkenaktualisierung und das Label es nicht mal schafft zu reagieren, d.h. immer auf 0% bleibt oder abrupt auf 80-100% springt, kann man davon ausgehen, dass ...

... du Control.BeginInvoke immer noch zu oft aufrufst. Dass dann das GUI blockieren kann, steht aber auch in [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) letzter Absatz in "Die Lösung".

Das wird dann auch die Ursache dafür sein, dass du Probleme mit dem Control.Invoke am Ende bekommst. Das, oder eine Nachwirkung einer Racing Condition, denn die Aktion wird bei Control.BeginInvoke wird ja im Gegensatz zu Control.Invoke asynchron zum aufrufenden Threads ausgeführt.

Bin langsam echt genervt von diesem Thema 😦

Ich auch. Threading an sich ist natürlich an sich kein leichtes Thema. Aber das Grundprinzip ist einfach zu verstehen und aus diesem Verständnis ergibt sich alles weitere. Erstelle mal ein Miniprojekt nur mit dem Hauptform und dem Fortschittsform und einem Thread, der die langlaufende Aktion und die vielen Events simuliert. Bringe das zum Laufen. Dann weißt du wie es geht und kannst das auf dein echtes Projekt übertragen.

herbivore

4.221 Beiträge seit 2005
vor 13 Jahren

Das mit dem Timer ist zwar eine schöne Idee finde ich aber nicht nötig wenn die Aktualisierung der ProgressBar nur dann stattfindet wenn sich der Prozentwert geändert hat (siehe oben). Das kannst du auch noch anpassen dass zB nur alle 5% eine Aktualisierung durchgeführt wird. Warum willst du also mit dem Timer das ganze noch verkomplizieren (wenn es dir so schon zu kompliziert ist wie du schreibst)?

@gfoidl

Ich habe ihn auf die Idee gebracht mit dem Timer... Der Timer hat nur den Vorteil, dass nicht in jedem call geprüft werden muss ob jetzt die Progressbar geuppt werden soll...

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Programmierhans,

ich weiß dass die Idee von dir stammt und die ist auch gut, nur eben nicht wenn die ProgressBar prozentuale Werte darstellt - da ergibt sich für zuviel Redundanz. Bei absoluten Werten finde ich sie besser da so zeitlich gefiltert wird.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

4.221 Beiträge seit 2005
vor 13 Jahren

@gfoidl

Das Problem bei prozentualen Updates ist, dass auch dann irgendwo der Invoke gemacht werden muss... dieser Invoke bremst dann den calling-Thread trotzdem aus (wenn auch nur 1 mal pro Prozent... ausser natürlich bei einem BeginInvoke).... deshalb habe ich da nicht differenziert. Zudem muss dann natürlich im calling-Context berechnet werden ob geuppt werden soll ... beim Timer fallen alle diese Restrictions weg.

Daher kann es sein, dass die Timer-Variante bei sehr hohem Event-Aufkommen (bei wildem Geballer) ein besseres Verhalten zeigt.

Gruss
Programmierhans

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren

Hey,

also ich hab nun eine prozentuale Änderung von 5%-Schritten (1% war zu viel) und mit Invoke, nicht BeginInvoke, da ansonsten die Fortschrittsform nicht mehr geschlossen wird, wenn der Thread sehr sehr wenig zu tun hat. Der GUI-Finish-Handler wird dann einfach nicht mehr aufgerufen bei kleinem Arbeitsvolumen des Threads und BeginInvoke. Invoke wird dann genau 20 mal aufgerufen (habe ich auch überprüft).

Das ist auch ein echtes Problem, weil ich vorher nicht wissen kann, ob der Thread nun mega viel zu tun hat oder total wenig. Wenn ich alle 5%-Schritte mit BeginInvoke anzeigen lasse und der Thread sehr wenig zu tun hat, schließt sich das Fenster nicht, wenn der Thread jedoch genug zu tun hat, funktioniert es. Daher habe ich nun max. und min. 20mal lieber ein Invoke gemacht als ein BeginInvoke zur Aktualisierung der Fortschrittsbalken und das funktioniert soweit ganz gut.

Ist das nicht auch der Grund warum Programme teilweise bei großem Arbeitsvolumen kein Fortschritt anzeigen, weil dieser einfach vom User variabel ist, sprich ich z. B. einmal eine Datei einlese die 2GB groß ist und beim nächsten mal nur noch 2MB? Wenn das Programm dann mit BeginInvoke arbeitet und z. B. in 2%-Schritten aktualisiert, dann funktioniert das bei der 2GB Datei vielleicht problemlos, aber bei der 2MB-Datei oder gar einer 100kB Datei vielleicht überhaupt nicht mehr. Oder hab ich da wieder mal ein denkfehler? 🤔

U
1.688 Beiträge seit 2007
vor 13 Jahren

z. B. in 2%-Schritten aktualisiert, dann funktioniert das bei der 2GB Datei vielleicht problemlos, aber bei der 2MB-Datei oder gar einer 100kB Datei vielleicht überhaupt nicht mehr. Oder hab ich da wieder mal ein denkfehler? 👶

2% sind 2%. Es kommt ggf. darauf an, worauf sie sich beziehen (Anzahl der Dateien oder deren Größe) und darauf, wann die Anzeige aktualisiert wird (z. B. immer nach dem Kopieren - dann dauert's bei 2GB länger). Aber irgendwann sind's eben 100%.
Warum der Fortschritt mit Invoke und BeginInvoke bei Dir so grundlegend anders ist? Schwer zu raten ohne Code.

S
sharpType Themenstarter:in
228 Beiträge seit 2009
vor 13 Jahren

Ich habe jetzt tatsächlich mal anstatt den 5%-Schritten den Timer eingebaut und ich muss sagen, das läuft alles BESTENS! (200-500ms im Intervall)

Damit meine ich, alles läuft rund und alle Handler werden ordnungsgemäß ausgeführt, unabhängig von dem Arbeitsaufwand.

Daher kann es sein, dass die Timer-Variante bei sehr hohem Event-Aufkommen (bei wildem Geballer) ein besseres Verhalten zeigt.

Ja!, das kann ich nun gut bestätigen. Mein Eventgeballer war wohl wirklich zu viel, selbst für BeginInvoke und 5er Prozentschritten und das hat dann Schwierigkeiten gemacht bei magerem Arbeitsaufwand im Thread. So komme ich mit dem Timer komplette ohne Invokes aus, nur einmal im Finish-Event-Handler und 1-2 mal im Thread um ein Label umzuschreiben. Das ist also für mich die beste Lösung!

Vielen, vielen Dank euch! Hut zieht 🙂