Informationen sicher vom Mainthread IN einen Thread hineinbringen

    Informationen sicher vom Mainthread IN einen Thread hineinbringen

    Hallo,

    ich kann inzwischen recht zuverlässig Informationen aus einem Thread in den Mainthread bringen.
    Das geht prima mit Sendmessage(), da das ja blockierende Eigenschaften hat.
    So weit so gut.
    Jetzt das Gegenteil : Wie bringe ich zuverlässig und gutem Programmirstil entsprechend Informationen IN einen Thread ?
    Konkret geht es um ein Signal zum Verlassen einer Schleife.
    Ich kann zwar eine dem Thread bekannte Schaltvarieble versuchen zu setzen, z. Bsp. über einen Button "Stop",
    aber ob und wann der Buttonevent auch ankommt steht in den Sternen.
    Ergo: Das ist wohl nicht die zünftige Art.
    Ich habe mir nach einem Tip hier eine mit Hilfsvariablen ausgestattete Klasse von TMultiReadExclusiveWriteSynchronizer
    abgeleitet, und es scheint zu funktionieren.

    Definition:

    Delphi-Code

    1. Type TSyncDat = class(TMultiReadExclusiveWriteSynchronizer)
    2. public
    3. isrunning : boolean;
    4. constructor Create;
    5. end;


    Im Mainthread den Hintergrundthread zum Verlassen seiner Schleife bewegen geht dann ganz einfach:

    Delphi-Code

    1. procedure TITKHF.btStopClick(Sender: TObject);
    2. begin
    3. Try
    4. ZOLL.BeginWrite;
    5. ZOLL.isrunning:=false;
    6. finally
    7. ZOLL.EndWrite;
    8. end;
    9. btLOS.Enabled:=true;
    10. end;


    Bei Bedarf kann mann dann noch weitere Hilfsvariablen setzen.
    Ist das ein "legaler" Weg ? Praktikabel scheint er ja zu sein...
    ism
    Morgen ist Heute schon Gestern
    Was ist denn das Kriterium an "Legalität"? Funktioniert und ist in gewissem Maße auch sinnvoll, wieso also nicht.
    Nur, um dir Alternativen aufzulisten, ohne Anspruch auf Vollständigkeit oder dass das besser ist:
    • Der Multidingser ist im Prinzip eine Kapselung von Events. Ein Event ist eine bestimmte Art von Handle, das zwei Status haben kann, "gefeuert" und "deaktiviert". Es ist threadsafe und mittels WaitForSingleObject kann darauf gewartet werden, dass es gefeuert wird (was mit SetEvent geschieht). Am besten die Beschreibung zu CreateEvent lesen.
    • Dann gibt es Mutexen, die funktionieren so ähnlich. Der Unterschied ist, dass eine Mutex benannt werden kann und deshalb zwischen verschiedenen Prozessen geteilt. So werden bspw. Programme realisiert, die sich nur einmal starten lassen: Die zweite Instanz kuckt einfach, ob es die Mutex schon gibt (Tipp System > Prozesse > Mehrfachstart verhindern). Außerdem sind Mutexen konzeptionell etwas anders gedacht, im Sinne einer Sperrung eines Bereichs, während Event ja darauf abzielen, Ereignisse mitzuteilen. Deshalb gibt es auch keine SetMutex-Funktion, sondern eine Mutex wird erstellt, geöffnet und freigegeben. CreateMutex
    • Weiterhin gibt es Semaphoren. Die sind von der Handhabe erweiterte Mutexen, nämlich solche, bei denen man bei Erstellen festlegt, wie oft eine Semaphore geöffnet werden kann/muss/soll. CreateSemaphore
    • Da wären noch kritische Sektionen. Das sind quasi Mutexen auf einer viel kleineren Ebene. Auch verwandt mit Events. Vor einem Codeabschnitt wird EnterCriticalSection aufgerufen, danach Leave. Der Record (die kritische Sektion) ist allen Threads gemein; damit kann eine Sektion nur von einem Thread betreten werden.
    • Schließlich gibt es Interlocked-Operationen. Die sind zwar nochmal ein bisschen anders, haben aber auch mit der Synchronisation zu tun. Letztlich muss ja jede Threadsynchronisation irgendwie hardwareseitig abgesichert werden. Das ist möglich, weil es atomare Operationen gibt, die auf einem Hardwareteil (dem Speicher-Bus) durchgeführt werden, welches es nur einmal gibt und die während ihrer Ausführung nicht unterbrochen werden können. Die normale Änderung eines Wertes an eine Variable ist nicht unbedingt atomar (Lesen des alten Wert, Verändern, Setzen). Aber InterlockedIncrement/Decrement (und in neuer Delphis gibt es glaube ich AtomicIncrement/Decrement als Alias dazu) sind es: Sie erhöhen/erniedrigen den Wert einer Integervariable um eins und liefern den alten Wert zurück, und die ganze Operation ist threadsafe. Die Anweisungen werden von Delphi in Assembler-Code direkt umgesetzt [in neueren Versionen inline] - da es nur eine einzige Anweisung ist [ich habe gerade nachgeschaut, in D7 sind es vier, ich meine mich aber zu erinnern, dass es in XE3 nur eine ist], ist das die schnellste Art der Synchronisation. Da aber sämtliches "Beiwerk" fehlt, was man gerne hätte (ggf. warten, ...), ist es aber nicht unbedingt die praktischste.
    • Soweit die Low-Level-Routinen. Diese werden von verschiedenen Klassen gekapselt:
      • Wie schon erwähnt SysUtils.TMultiReadExclusiveWriteSynchronizer für zwei Events gleichzeitig, eins zum Lesen und eins zum Schreiben.
      • Wer nur ein Event braucht (so sieht es bei dir danach aus), kann SyncObjs.TEvent verwenden.
      • SyncObjs.TCriticalSection ist eine Kapselung von Enter/LeaveCriticalSection.
      • Mir ist jetzt keine Kapselung von Mutexen, Semaphoren und Interlockeds bekannt, aber bestimmt irre ich mich hier.
    Vielleicht hat das aber auch alles gar nichts mit deiner Frage zu tun. Deshalb mal schnell meine Interpretation:
    1. In einem Thread A läuft in irgendeiner Schleife irgendeine Aktion. Du hast einen Schleifenkopf, der überprüfen soll ob (a) die Arbeit getan ist oder (b) der Benutzer keine Lust mehr hatte.
    2. In Hauptthread B hat der Benutzer die Gelegenheit, abzubrechen.
    Du willst wissen, wie du am einfachsten A mitteilst, dass in B ein Klick gemacht wurde. Hierfür brauchst du eigentlich überhaupt keine Synchronisation. Wenn du nur eine Variable setzen willst, dann ist das - sofern die Variable klein genug ist, dass das in einer Operation geht - threadsafe. Du deklarierst also einfach in A eine public-Variable Cancelled oder wie auch immer, die der Thread auf False initialisiert und in B wird die Variable auf True gesetzt. Das ist eine atomare Speicheraktion, da geht nichts schief. Möglicherweise funktioniert das nicht. In Delphi Berlin gibt es nun endlich das Attribut volatile, wenn du das verwendest, klappt es auf jeden Fall. Hast du nicht das neueste Delphi, kann es passieren, dass der Compiler deinen Code optimiert und den Inhalt der Variable am Anfang der Prozedur von A in ein Register schreibt und am Ende erst in die Variable. Das habe ich zumindest mal gehört, erlebt habe ich es noch nie. Wahrscheinlich wird das bei Klassenvariablen auch gar nicht passieren, ebenso nicht, wenn du genug Variablen in deiner Funktion hast, dass keine Register frei sind und und und. Das ist m.E. eine ziemlich sichere Sache. Sollte es schief gehen, kannst du die Variable immer noch über eine Getter-Funktion abfragen.
    Letztes: "aber ob und wann der Buttonevent auch ankommt steht in den Sternen." Wieso? Wenn deine Schleife entsprechend lang ist, dauert es natürlich. Die einzige Möglichkeit1, den Thread zu beenden auch wenn er gerade in einer Schleife steckt, ist über TerminateThread. Dann ist sofort Schluss, aber finalisiert wird da auch nichts mehr; das Betriebssystem räumt auf.

    1 Abgesehen von Halt, Shutdown oder Ähnlichem...
    Master of the EDH ;)
    Hallo,

    und vielen Dank für die Antworten. Also kann ich das so machen wie beschrieben.
    Ich habe halt den Ehrgeiz, den Thread sow wenig wie möglich über die Anwendung missen zu lassen.
    Selbst Controls, die ich in Synchronize(Updateproc) anspreche werden als Stellvertreter "reingereicht":

    Delphi-Code

    1. meinThread.dasMemo:=Hauptformular.EigentlichesMemo;

    Grund: Es könnte ja sein, daß der Thread in einer anderen Anwendung nachgenutzt werden soll.
    Dann schönes Wochenende
    Morgen ist Heute schon Gestern
    Schon, nur die Kapselung würde ich schon noch etwas ändern. Erstens brauchst du keinen ReadWriteSynchronizer (vermute ich), weil dir eine kritische Sektion reicht. Und dann sollte eine Variable, die nur umschlossen von Schutzblöcken geändert werden kann, private sein und eine Methode das entsprechend besorgen.
    Falls du aber tatsächlich nichts anderes machen willst als deine Boolean-Variable zu ändern, brauchst du gar kein Konstrukt in der Art, weil ZOLL.isRunning := False bereits threadsafe ist. Das ist eine Anweisung, die einen 32-Bit Wert auf eine Konstante setzt. Problematisch ist es nur, wenn diese Variable in einem Packed Record drinsteckt, weil sie dann nicht unbedingt mehr an den Speichergrenzen ausgerichtet ist und deshalb Schutzblöcke braucht. Aber als Objektvariable richtet Delphi grundsätzlich aus, damit ist das Schreiben einer Konstanten eine atomare Operation.
    Master of the EDH ;)