GUI- und WorkerThread

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
mrz
Beiträge: 79
Registriert: 07.08.2008, 14:34

Re: GUI- und WorkerThread

Beitrag von mrz »

Krishty hat geschrieben:Bezog sich auch nicht auf deine Antwort; ich will nur, dass joggel einen Überblick darüber bekommt, was er da tut :)
Also wenn ich mir den Code von BeRsErKeR anschaue gruselt es mir schon:

Der Member "bool shutdown" müsste volatile sein.
Und "listOfLogicActions" ist eine normale List,
also auch kein Garantie betreffend Thread visibility.

Das Beispiel könnte man viel simpler machen indem man einfach eine BlockingCollection<T> verwendet und im Loop Take() aufruft:
https://msdn.microsoft.com/en-us/library/dd381908.aspx

Auszug aus dem Doc:

A call to Take may block until an item is available to be removed or the token is canceled.

Mit sowas wäre es zumindest nicht komplett falsch.
Das ganze Beispiel ist aber vom Lösungsansatz her schon nicht schön,
Thema "besser non-blocking Code schreiben".

Hinweise wie man das sauberer machen kann gabs ja bereits,
fängt z.B. an dass man direkt oder indirekt ein Threadpool verwendet.

Mehr zum Thema:
http://bholley.net/blog/2015/must-be-th ... -code.html
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

Ich werde mir das alles mal morgen genauer anschauen.
Ich danke euch erstmal.
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

So, habe mir das mal angeschaut.
Der Link von MasterQ sieht mir ziemlich nützlich aus. Danke

Die Variante von Berserker gefällt mir auch gut, aber anscheinend ist das noch keine endgültig "sauber" und sichere Lösung.
Weil ja die Liste nicht Thread-Save ist...oder?


@Thread-Pool
Muß ich mich wohl mal damit beschäftigen. Da ich keine Ahnung habe was genau damit gemeint ist...


@non-blocking Code (mrz)
Wie würde man denn sowas machen? Gibt es dazu Beispiele? Naja...ich kann ja mal Google befragen.


@Krishty
Klingt nach einem Promise oder einem Future. (Ganz ab davon, dass solche Dialoge im selben Thread laufen können, so lange sie modal sind!)
Wie würde man das denn umsetzen was ich vor habe, ohne einen zusätzlichen Thread?


@Allgemein
Was haltet ihr denn eigentlich von der Idee, dass ich GUI und Worker voneinander trennen will?
Also, dass in den GUI Klassen eigentlich nur sowas wie "Actions" aktiviert werden, die aber an einer anderen Stellen erst verarbeitet werden.
Ich möchte quasi die GUI-Klassen nur für die Interaktion benutzen; in ihr soll keine Programmlogik stattfinden...
Meinungen?
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

mrz hat geschrieben:
Krishty hat geschrieben:Bezog sich auch nicht auf deine Antwort; ich will nur, dass joggel einen Überblick darüber bekommt, was er da tut :)
Also wenn ich mir den Code von BeRsErKeR anschaue gruselt es mir schon:

Der Member "bool shutdown" müsste volatile sein.
Und "listOfLogicActions" ist eine normale List,
also auch kein Garantie betreffend Thread visibility.

Das Beispiel könnte man viel simpler machen indem man einfach...
Ich habe lediglich joggels ursprünglichen Code erweitert. Selbst löse ich das auch ganz anders. Im Idealfall trenne ich Konfiguration (UI) und Abarbeitung (Logik) nicht nur durch Threads, sondern auch zeitlich. Soll heißen, ich konfiguriere zuerst und die komplette Logik läuft nach einem Event und die GUI schläft dann so lange. Kommt natürlich immer auf den Anwendungsfall an. Mir ist aber noch nicht wirklich oft etwas untergekommen, wo es sinnvoll war bei aktiver UI größere Logikprozesse im Hintergrund laufen zu lassen. Und wenn doch, bin ich mit einem BackgroundWorker immer ganz gut gefahren, den ich dann entsprechend anlaufen lasse. Parallel da schon pauschal irgendwelche Threads laufen zu lassen, finde ich persönlich nicht wirklich schön. Wenn ich etwas parallel anstoßen will, dann tu ich das doch auch genau an dieser Stelle.

mrz hat geschrieben:Der Member "bool shutdown" müsste volatile sein.
Nö, wieso? Das wird nur vom Main-Thread verändert. Andere Threads greifen da nur lesend drauf zu. Ein gleichzeitiger Zugriff mehrerer Threads ist zudem durch das Wait nicht möglich. Prinzipiell mag das vielleicht korrekt sein, aber das ist eine schnelle Anpassung von joggels Code. Und das passt imho schon so für diesen einfachen Fall.
Ohne Input kein Output.
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

BeRsErKeR hat geschrieben:Selbst löse ich das auch ganz anders. Im Idealfall trenne ich Konfiguration (UI) und Abarbeitung (Logik) nicht nur durch Threads, sondern auch zeitlich. Soll heißen, ich konfiguriere zuerst und die komplette Logik läuft nach einem Event und die GUI schläft dann so lange.
Kann man mal nach etwas Beispiel-Code fragen?
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Es kommt drauf an, was genau du vorhast. Dein Anliegen war die Logik von der UI zu trennen. Die Frage ist, wie du das genau meinst. Ich nehme an du meinst, dass die Logik nicht im UI-Thread (Main-Thread) läuft, damit die Oberfläche nicht blockiert wird, während die Logik läuft.

Und genau hier stellt sich die Frage, ob es sinnvoll ist, dass der Anwender die UI weiter bedienen kann, während die Logik läuft. Oft ist es ja so, dass der Anwender erst nach der Abarbeitung weitermachen will und kann. In dem Fall, sollte die UI sogar blockiert werden. Zum Beispiel indem man die Controls für die Zeit disabled. Asynchrone Abarbeitung ist ja eher dann sinnvoll, wenn dem Anwender egal ist, wann diese fertig wird und er parallel schon weiterarbeiten kann. Das sieht mir in deinem Beispiel aber nicht so aus.

Die erste Frage sollte also sein, ob hier ein paralleler Thread überhaupt sinnvoll ist. Wenn der Anwender eh auf das Ergebnis der Abarbeitung wartet, dann sehe ich da keinen Sinn drin. Falls die Abarbeitung sehr lange dauert und ggf. vorzeitig abgebrochen können werden soll, dann kannst du über einen BackgroundWorker nachdenken, den du startest, aber dennoch den Großteil der UI disablest.

Beispielcode ist da schwierig ohne dein genaues Ziel zu kennen. Die Anforderung "Logik von UI trennen" ist nicht sehr aussagekräftig und im Kontext von Threading imho auch nicht immer sinnvoll.
Ohne Input kein Output.
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

Naja...ich hielt (und halte das auch noch etwas) für kein sooo gutes Design. Also Logik-Code in UI-Code zu packen...
Mir ging es darum, dass ich die GUI wirklich nur für die Interaktion benutze.
Klar, ich könnte dann in der Funktion, die bei einem Klick auf den Button aufgerufen wird, einfach eine Funktion aufrufen, die das Öffnen + Lesen der Datei übernimmt;
Aber intern würde sich das halt alles innerhalb der onButtonClick-Funktion abspielen....was ich nicht so schön fand.

Mir geht es eher darum, ein "schönes" SW-Design zu konstruieren, in der die GUI eben nur für GUI-Sachen zuständig ist, und alles was nicht GUI ist gehört nicht in den GUI-Code.
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Naja Code-Trennung hat ja erstmal nichts mit Threads zu tun. Dass du deinen Logik-Code z.B. in eine andere Klasse auslagerst, ist ja erstmal nicht verkehrt.

Wenn du jetzt aber z.B. die Logik in einen Extra-Thread packst und am Ende dann im UI-Thread auf diesen Thread wartest, hast du auch nichts gewonnen. Die Frage ist auch, ob du dir das Leben künstlich schwer machen willst, nur um "gutes Design" zu erreichen.

Wenn es dir so wichtig ist, dann starte doch für jede Logik-Task einen BackgroundWorker und hänge dich an das RunWorkerCompleted-Event, um festzustellen wann er fertig ist. Währenddessen kannst du z.B. Teile des UIs disablen etc. Für mich sieht das aber eher nach "mit Kanonen auf Spatzen schießen" aus.
Ohne Input kein Output.
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

Ja, kann sein das ich da etwas zu viel des Guten will...

Ich werds nochmal überdenken.
Wobei ich den grundgedanken "Logik und UI trennen" ja echt nicht verkehrt finde.
Werd mich da auf jeden Fall noch etwas mit der Thread/Task/etc Geschichten belesen...
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Wie gesagt der Grundgedanke "Logik und UI trennen" ist schon gut, aber er hat nichts mit Threading zu tun, sondern mit der Trennung von Code.

Eine gute Übung ist hier z.B. die gesamte Logik in einem separaten Assembly (DLL) zu implementieren und dort auf alle UI-spezifischen Referenzen (System.Drawing, System.Windows.Forms, etc) zu verzichten. In deiner UI-Assembly kommunizierst du mit dem Logic-Assembly. Dort sollten dann nur noch direkte UI-Zugriffe (Controls befüllen und lesen) und Schnittstellenaufrufe zur Logik auftauchen.

Ansonsten kannst du dir in diesem Kontext gern auch mal MVC oder MVVM angucken. Wie gesagt sagen diese Modelle etwas zur Code-Trennung aus, aber nicht dazu, dass der Code in verschiedenen Threads laufen muss.
Ohne Input kein Output.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: GUI- und WorkerThread

Beitrag von Krishty »

BeRsErKeR hat geschrieben:
mrz hat geschrieben:Der Member "bool shutdown" müsste volatile sein.
Nö, wieso? Das wird nur vom Main-Thread verändert. Andere Threads greifen da nur lesend drauf zu.
Eben – alle lesenden Threads lesen dann den falschen Wert ;)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
mrz
Beiträge: 79
Registriert: 07.08.2008, 14:34

Re: GUI- und WorkerThread

Beitrag von mrz »

Krishty hat geschrieben:
BeRsErKeR hat geschrieben:
mrz hat geschrieben:Der Member "bool shutdown" müsste volatile sein.
Nö, wieso? Das wird nur vom Main-Thread verändert. Andere Threads greifen da nur lesend drauf zu.
Eben – alle lesenden Threads lesen dann den falschen Wert ;)
@BeRsErKeR
Wenn "shutdown" nicht volatile ist dann hast Du keine *Garantie* dass Änderungen für andere Threads sichtbar werden.

Also im MainThread wird irgendwann shutdown auf true gesetzt, für den anderen Thread ist shutdown aber immer noch auf false
und er sieht *evtl* auch nie dass shutdown auf true gesetzt wurde.

Hat mit CPU Caches zu tun.

Natürlich gibt es noch andere Möglichkeiten Sichtbarkeit zu garantieren,
statt volatile könnte man z.B. alle Zugriff auf "shutdown" mit eines Lock machen.

Der Vollständigkeit halber (sonst kommt gleich Krishty ;)) sei noch zu erwähnen dass es, je nach Memory Model zur Runtime,
es spezielle Fälle - oder besser Szenarios - gibt wo *eigentlich* keine Sychronisation nötig ist (Happens-before Regeln).
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: GUI- und WorkerThread

Beitrag von Krishty »

mrz hat geschrieben:Hat mit CPU Caches zu tun.
Nein, mit dem Compiler. Die Sache läuft so ab:
  1. DoThisAllTheTime() muss optimiert werden.
  2. Es benutzt eine Variable shutdown.
  3. shutdown beginnt false und wird ausschließlich in MainForm_Closed() verändert.
  4. MainForm_Closed() wird in DoThisAllTheTime() niemals aufgerufen.
  5. Also wird shutdown niemals geändert.
  6. Also darf der Optimizer alle Vorkommen von shutdown durch die Konstante false ersetzen.
Resultat: Wenn der eine Thread dann shutdown = true setzt, passiert in dem anderen Thread nichts.

volatile teilt dem Compiler mit, dass sich shutdown eben doch ändern kann, auch wenn es nicht durch den aktuellen Thread geschrieben wird.

Die CPU-Caches spielen da erstmal keine Rolle; tatsächlich bewirkt volatile aber auch, dass der Compiler die Caches synchronisiert, bevor er aus shutdown liest. Auf x86 sind die Caches aber sowieso immer synchron* und da muss nichts gemacht werden.

* Außer bei SSE 2-Befehlen, die die Cache-Hierarchie umgehen; sowas benutzt der C#-Compiler aber nicht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Hm soweit ich meinte zu wissen, sorgt das WaitAll (genau wie ein lock) dafür, dass die Werte danach aktuell sind, weil es eine memory barrier einführt. An die Compiler-Optimierung habe ich nun nicht wirklich gedacht. Wobei mich diese Optimierung wundern würde, weil selbst für Single-Thread wäre das ja dann ein Problem. Wenn ich eine Variable z.B. in 2 Events benutze, in einem davon setze und in einem anderen auswerte, dann wird das eine Event das andere auch nicht aufrufen - aber eine Optimierung wäre fatal.
Ohne Input kein Output.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: GUI- und WorkerThread

Beitrag von Krishty »

Ich kenne die Event-Semantik in C# nicht, aber normalerweise (Windows-Nachrichten? Dispatch-Interface?) werden die seriell ausgewertet statt parallel. D.h., so lange das eine Event nicht die Message Pump aufruft oder anderweitig die Kontrolle an Runtime/OS abgibt, kann das andere Event unmöglich laufen, und demnach auch nicht die Variable ändern. Damit wäre die Optimierung korrekt.

Es gibt natürlich genug andere Fälle, die explizit nebenläufig sind. Dann braucht man aber entweder Compiler-Unterstützung (ich kann mir gut vorstellen, dass der Compiler Variablen implizit als volatile behandelt, wenn sie von einem Lambda benutzt (captured) werden, das man an was Asynchrones übergibt – dann hat man es ja eh nicht mehr mit „normalen“ Variablen zu tun, auch wenn es der Compiler so aussehen lässt – aber mein C#-Wissen ist hier aber erschöpft) oder eben explizites volatile, wie mrz anmerkte.

Die Moral ist übrigens für joggel, so viele fertige Schnittstellen wie möglich zu nutzen, denn Multithreading selber machen ist – das sehen wir gerade – knifflig …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Ich bin mir da auch nicht 100% sicher, wie die Event-Semantik ist. Tatsache ist jedenfalls, dass man non-volatile Membervariablen in völlig unabhängigen Events korrekt verwenden kann, ohne dass da was optimiert wird. In Hinblick auf Events habe ich in C# aber schon gruselige Sachen gesehen. Die Controls basieren ja mal auf den guten alten WinAPI-Controls und teilweise sind sie rein in .NET implementiert. Im CallStack von Events sieht man gerne mal ein Stück nativen Code dazwischen. Was da passiert ist nicht immer so ganz eindeutig. Ob da alles nur im UI-Thread läuft, weiß ich auch nicht so genau.

Eine sichere Alternative zu volatile wäre auch eine Property an Stelle einer Variablen. Das macht der BackgroundWorker übrigens auch mit CancellationPending. CancelAsync() setzt intern ein Flag und der Worker-Thread prüft es über die Property. Dadurch ist es dann auch thread-safe.
Ohne Input kein Output.
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

Krishty hat geschrieben: Die Moral ist übrigens für joggel, so viele fertige Schnittstellen wie möglich zu nutzen, denn Multithreading selber machen ist – das sehen wir gerade – knifflig …
Jawohl...kam an^^
Aber das Thema finde ich trotzdem interessant...
mrz
Beiträge: 79
Registriert: 07.08.2008, 14:34

Re: GUI- und WorkerThread

Beitrag von mrz »

Krishty hat geschrieben:
mrz hat geschrieben:Hat mit CPU Caches zu tun.
Nein, mit dem Compiler. Die Sache läuft so ab:
  1. DoThisAllTheTime() muss optimiert werden.
  2. Es benutzt eine Variable shutdown.
  3. shutdown beginnt false und wird ausschließlich in MainForm_Closed() verändert.
  4. MainForm_Closed() wird in DoThisAllTheTime() niemals aufgerufen.
  5. Also wird shutdown niemals geändert.
  6. Also darf der Optimizer alle Vorkommen von shutdown durch die Konstante false ersetzen.
Resultat: Wenn der eine Thread dann shutdown = true setzt, passiert in dem anderen Thread nichts.

volatile teilt dem Compiler mit, dass sich shutdown eben doch ändern kann, auch wenn es nicht durch den aktuellen Thread geschrieben wird.

Die CPU-Caches spielen da erstmal keine Rolle; tatsächlich bewirkt volatile aber auch, dass der Compiler die Caches synchronisiert, bevor er aus shutdown liest. Auf x86 sind die Caches aber sowieso immer synchron* und da muss nichts gemacht werden.

* Außer bei SSE 2-Befehlen, die die Cache-Hierarchie umgehen; sowas benutzt der C#-Compiler aber nicht.
Nein weder der Compiler noch der Optimizer darf überall shutdown mit false ersetzen.
C# hat wie Java so ein Ding welches sich Reflection schimpft.
In Java darf das nur gemacht werden wenn die Variable "final" (=Immutable) ist.
In C# müsste es dafür "const" geben.
Zumindest in Java hat man btw sogar die Möglichkeit mit Reflection den final Modifier zu entfernen damit man dann auch final Variablen ändern kann.
Allerdings muss man damit rechnen dass ggf Änderungen an der Variable "niemand sieht" (weil schon ersetzt/optimiert wurde).
Die JVM tut teilweise "final" auch nicht komplett als Immutable betrachten (und somit nur bedingt optimiert werden kann),
dass kommt wegen dem Serializable Support.

Grundsätzlich hast Du aber natürlich Recht dass volatile vom Compiler ausgewertet wird und nur indirekt Einfluss auf die Caches hat.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: GUI- und WorkerThread

Beitrag von Krishty »

Alles durch false zu ersetzen ist auch weit hergeholt, das muss ich zugeben. (Unter C++ schafft es nicht einmal Clang, unbenutzte Felder aus Klassen wegzuoptimieren.) Außerdem habe ich die Trennung von Compiler & JITter weggelassen, das ist auch nicht so pralle.

Die weitaus wahrscheinlichere Gefahr ist, dass die Variable in einem Register landet, und deshalb nur bei Betreten der Funktion einmal ausgewertet wird. (Sowas ist nur noch umständlicher zu erklären, weil deutlich mehr Faktoren in die Entscheidung hereinspielen.)

Wenn ich sowas lese wie hier auf StackOverflow, scheint das auch tatsächlich zu passieren (obwohl ich vor lauter Edits nicht mehr verstehe, ob der 2. Aufruf nun im Register landete oder ob’s das Inlining war).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten