GUI- und WorkerThread

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
joggel

GUI- und WorkerThread

Beitrag von joggel »

Huhu,

ich überarbeite eine Anwendung. Ich möchte jetzt, dass das GUI vollkommen losgelöst von meiner Logik existiert.
Ich arbeite mit C# und Winforms. Aber das ist auch erstmal egal.

Ich habe mir dazu folgendes überlegt:
Etwas Code, da Code meist mehr sagt als 1000 Worte <3

Code: Alles auswählen

   public partial class MainForm : Form
    {
        // in diesem Thread findet die ganze arbeit statt. der HauptThread wird nur für das darstellen und die
        // Interaktion mit dem GUI verwendet
        System.Threading.Thread logicThread;

	listOfLogicActions List<ActionBase> = new List<ActionBase>();
		
        public MainForm()
        {
            InitializeComponent();

            logicThread = new System.Threading.Thread(DoThisAllTheTime);
            logicThread.Start();

        }

        private void ButtonOpenProtocol_Click(object sender, EventArgs e)
        {
            // Displays an OpenFileDialog so the user can select a data file.
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Multiselect = false;
            openFileDialog.Filter = "blaBla";

            if ((openFileDialog.ShowDialog() == DialogResult.OK) &&
                (openFileDialog.CheckFileExists == true))
            {
				
				listOfLogicActions.Add(new ActionOpenFile(openFileDialog.FileName));
            }
        }



        public void DoThisAllTheTime()
        {
              while(true)
              {
                for(int index=0; index<listOfLogicActions.Count; ++index)
		{
			listOfLogicActions[index].doIt();
		}
              }
        }
    }
So mein erster Gedanke.
Jetzt rattert der logicWorkerThread aber die GANZE ZEIT...

Meine Frage:
Kann ich das irgendwie anders lösen, dass nicht die ganze Zeit eine Schleife durchratter, sondern das das Durchlaufen der listOfLogicActions nur angestoßen wird, wenn ein neuer Eintrag vorhanden ist?
Zuletzt geändert von joggel am 20.07.2017, 13:39, insgesamt 1-mal geändert.
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 »

Normalerweise richtet man sich ein Event ein, auf das der Arbeiter wartet. Nachdem etwas Neues in der Schleife ist, wird das Event ausgelöst (wird signalled) und der Arbeiter hört auf zu warten und geht an die Arbeit. Wenn keine Arbeit mehr da ist, wartet er wieder auf das Event.

Konkret in deinem Fall kann ich mir aber vorstellen, dass C# schon irgendwelche Listenklassen hat, die von Haus aus waitable sind oder so.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

Danke Krishty, das mit dem Event war ein guter Hinweis.
Ein Tutorial
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

Nein, irgendwie ist das auch sinnlos für mich!
Die aktuelle Arbeit wird ja auch bei einem Change-Event unterbrochen ==> und das möchte ich ja nicht!!

Oder ist das überhaupt sinnvoll was ich vorhabe :?
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Warum startest du denn nicht einfach den Thread (oder besser einen BackgroundWorker), wenn du eine spezifische Aktion ausführen möchtest? Ansonsten würde ich, wie Krishty schon sagte, auf ein Signal warten und nicht permanent einen Loop durchlaufen lassen.

Schau dir dazu am besten mal die WaitHandle-Klasse an. Da gibt es auch ein gutes Beispiel mit AutoResetEvents. Zusätzlich kannst du auch noch ein ManualResetEvent einbauen, was du z.B. beim Shutdown deiner Anwendung signalst. Damit der Thread auch sauber beendet, wenn du die Anwendung schließt.

Du kannst z.B. mit WaitAny auf 1 AutoResetEvent und 1 ManualResetEvent warten. Das erste signalst du, wenn du die Liste abarbeiten möchtest und das andere wenn die Anwendung schließt. Das WaitAny wird dann die Blockierung aufheben, wenn ein Signal von beiden auslöst. Für den 2. Fall (Anwendung schließt) musst du vor der inneren For-Schleife halt nochmal auf ein Shutdown-Flag oder ähnliches prüfen und entsprechend rausspringen. Das WaitHandle.WaitAny platzierst du z.B. innerhalb der Endlosschleife.
Ohne Input kein Output.
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

BeRsErKeR hat geschrieben:Warum startest du denn nicht einfach den Thread (oder besser einen BackgroundWorker), wenn du eine spezifische Aktion ausführen möchtest? Ansonsten würde ich, wie Krishty schon sagte, auf ein Signal warten und nicht permanent einen Loop durchlaufen lassen.
So wie ich das von Krishty verstanden habe, ist das soetwas wie ein Event.
Also die Liste merkt, dass ihr inhalt verändert wurde, und reagiert promt! Und da wird ja die vorherige/aktuelle Arbeit unterbrochen => und das wollte ich eigentlich vermeiden.

Bsp:
Ich drücke den "Öffne eine Datei"-Button, dieser zeigt mir ein Openfiledialog, mit dem ich eine Datei auswähle. Schluss!!! AUS!! Mehr möchte ich durch diese Aktion nicht tun!
Dann soll auch diese Funktion, die beim Klick auf den "Öffne eine Datei"-Button ausgeführt wird, auch wieder beendet werden.
Erst danach soll etwas mit der Datei geschehen, die ich ausgewählt habe.

Und wenn ich noch in dieser Funktion (Klick auf "Öffne Datei"-Button) einen Thread starte, gefällt mir das auch nicht so sehr.

Ich hätte gerne irgend etwas globaleres...
Bei Qt läuft das doch auch so ab, das die Signale erst in eine Liste gepackt werden, und dann zu einem späteren Zeitpunkt abgearbeitet werden....oder?

Vlt ist das auch totaler Unsinn was ich mir überlege.
Mein Ziel ist es eben, die Logik so weit wie möglich von der GUI-Interaktion zu lösen...
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Du kannst ja deinen "globalen" Thread so lassen. Aber anstatt, dass du permanent die Endlosschleife durchlaufen lässt, wartest du am Anfang der Endlosschleife auf ein Signal. Das Signal setzt du immer dann, wenn du eine Aktion für die Liste durchführen möchtest. Der GUI-Thread setzt also nur das Signal und läuft fröhlich weiter. Der Logik-Thread macht ohne Signal nichts außer Warten. Wenn ein Signal kommt, arbeitet er die Listenoperationen ab und wartet auf das nächste Signal. Der Charme am AutoResetEvent ist es, dass das Signal wieder automatisch auf den AUS-Zustand wechselt, sofern ein Wait-Aufruf das Signal verarbeitet hat.

Das zusätzliche Event beim Shutdown würde ich einfach einbauen, damit der Thread auch irgendwann mal beendet werden kann. Weil anders kannst du das Wait schlecht abbrechen. In deiner Version mit der Endlosschleife hat der Thread ja bislang auch wenig Chancen mal das Zeitliche zu segnen.
Ohne Input kein Output.
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

Ich hab hier leider gerade nichts zum Programmieren. Daher hier ungetestet:

Code: Alles auswählen

public partial class MainForm : Form
{
    // in diesem Thread findet die ganze arbeit statt. der HauptThread wird nur für das darstellen und die
    // Interaktion mit dem GUI verwendet
    System.Threading.Thread logicThread;

    List<ActionBase> listOfLogicActions = new List<ActionBase>();
    AutoResetEvent listActionEvent = new AutoResetEvent(false);
    ManualResetEvent shutdownEvent = new ManualResetEvent(false);
    bool shutdown = false;
                
    public MainForm()
    {
        InitializeComponent();

        logicThread = new System.Threading.Thread(DoThisAllTheTime);
        logicThread.Start();
    }

    private void MainForm_Closed(object sender, EventArgs e)
    {
        shutdown = true;
        shutdownEvent.Set();
    }

    private void ButtonOpenProtocol_Click(object sender, EventArgs e)
    {
        // Displays an OpenFileDialog so the user can select a data file.
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.Multiselect = false;
        openFileDialog.Filter = "blaBla";

        if ((openFileDialog.ShowDialog() == DialogResult.OK) &&
            (openFileDialog.CheckFileExists == true))
        {
            listOfLogicActions.Add(new ActionOpenFile(openFileDialog.FileName));
            listActionEvent.Set(); // Listenabarbeitung signalisieren
        }
    }

    public void DoThisAllTheTime()
    {
        while(true)
        {
            WaitHandle.WaitAny(new WaitHandle[] { listActionEvent, shutdownEvent });

            if (shutdown)
                return;
         
            for(int index=0; index<listOfLogicActions.Count; ++index)
            {
                listOfLogicActions[index].doIt();
            }
        }
    }
}
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 »

joggel hat geschrieben:Bsp:
Ich drücke den "Öffne eine Datei"-Button, dieser zeigt mir ein Openfiledialog, mit dem ich eine Datei auswähle. Schluss!!! AUS!! Mehr möchte ich durch diese Aktion nicht tun!
Dann soll auch diese Funktion, die beim Klick auf den "Öffne eine Datei"-Button ausgeführt wird, auch wieder beendet werden.
Erst danach soll etwas mit der Datei geschehen, die ich ausgewählt habe.
Klingt nach einem Promise oder einem Future. (Ganz ab davon, dass solche Dialoge im selben Thread laufen können, so lange sie modal sind!)

Statt selber Threads zu starten, würde ich auch den Thread Pool nutzen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
joggel

Re: GUI- und WorkerThread

Beitrag von joggel »

@Krishty
Naja...ist meine Anforderung. Will das "ordentlich" machen.

@Berserker
Danke. Werd mir das mal anschauen.
Aber läuft da in dem extra Thread nicht auch die ganze Zeit die while-Schleife?
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: GUI- und WorkerThread

Beitrag von BeRsErKeR »

joggel hat geschrieben:@Berserker
Danke. Werd mir das mal anschauen.
Aber läuft da in dem extra Thread nicht auch die ganze Zeit die while-Schleife?
Nein. Es ist natürlich nach wie vor eine Endlosschleife, aber der Thread wartet beim WaitAny bis ein Signal kommt und somit läuft da erstmal nichts weiter.
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 »

Krishty hat geschrieben:Klingt nach einem Promise oder einem Future.
joggel hat geschrieben:Naja...ist meine Anforderung. Will das "ordentlich" machen.
Promise und Future sind Programmierkonzepte. Dein Datei-Loader bekommt z.B. von der UI einen Pfad versprochen, und kann abwarten, dass die UI dieses Versprechen asynchron einlöst ;)
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 »

Krishty hat geschrieben:
Krishty hat geschrieben:Klingt nach einem Promise oder einem Future.
joggel hat geschrieben:Naja...ist meine Anforderung. Will das "ordentlich" machen.
Promise und Future sind Programmierkonzepte. Dein Datei-Loader bekommt z.B. von der UI einen Pfad versprochen, und kann abwarten, dass die UI dieses Versprechen asynchron einlöst ;)
Im Prinzip ist das Wait/WaitAny oben eine Art der Umsetzung des Future in C#. Man kann sowas natürlich auch anders lösen, z.B. mit IAsyncResult und Co. Aber für diesen einfachen Anwendungsfall geht imho auch ein Parallelthread + Wait + ActionList.
Ohne Input kein Output.
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: GUI- und WorkerThread

Beitrag von xq »

Ich poste hier einfach mal das hier, da es scheint, als würde es joggels anforderungen entsprechen:
https://docs.microsoft.com/en-us/dotnet/csharp/async
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
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:Im Prinzip ist das Wait/WaitAny oben eine Art der Umsetzung des Future in C#. Man kann sowas natürlich auch anders lösen, z.B. mit IAsyncResult und Co. Aber für diesen einfachen Anwendungsfall geht imho auch ein Parallelthread + Wait + ActionList.
Bezog sich auch nicht auf deine Antwort; ich will nur, dass joggel einen Überblick darüber bekommt, was er da tut :)
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: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
Antworten