Auf Thread ende warten



  • Hallo zusammen,

    damit eine längere Berechnung nicht meine UI blockiert habe ich dies in einen Thread ausgelagert. Der Einfachheit halber habe ich CreateThread() verwendet.
    Das ganze funktioniert soweit auch, allerdings benötige ich die Info wann der Thread beendet wurde.
    Dies kann ich mit WaitForSingleObject() ja abfragen.
    Wenn ich den Timeout jedoch auf unendlich setzte ist mein UI wieder blockiert.

    Irgendwo habe ich mal folgendes gefunden:

    const HANDLE hThread = CreateThread(NULL, 0, threadStartTest, NULL, 0, NULL);
        if(!hThread)
        {
            L.AddFmtDbg(Position, L"Thread error");
        }
        while(WaitForSingleObject(hThread, 100) == WAIT_TIMEOUT)
        {
            Application->ProcessMessages();
        }
    

    Das ganze funktioniert natürlich, jedoch ist mir nicht klar ob dies der richtige Weg ist bzw. ob es Alternativen gibt.

    MfG Stephan



  • Du verwendest doch die VCL, oder? Warum benutzt du dann nicht die TThread-Klasse und dessen OnTerminate-Ereignis?



  • Th69 schrieb:

    Du verwendest doch die VCL, oder? Warum benutzt du dann nicht die TThread-Klasse und dessen OnTerminate-Ereignis?

    Wie bereits geschrieben, der Einfachheit halber.
    Jedoch wenn ich dies auf die schnelle richtig gesehen habe, muß ich mit TThread trotzdem in meinem Mainthread irgendwie auf das Ende des Threads pollen, und genau dies ist meine Frage/Problem.
    Eventuell muß ich meine Programmlogik komplett anpassen 😢

    MfG Stephan



  • Du kannst das Handle benutzen wie ein Event und dann darauf warten.
    WaitForSingleEvent

    Wenn ich das noch richtig im Kopf habe. Ist einfach zu lange her.

    Ach mist. steht ja da. Na ist kurz vor ostern.



  • Warum sendest Du nicht einfach eine Nachricht aus dem Thread an die Anwendung, sobald der Thread beendet ist? Dann brauchst Du das WaitFor... nicht.



  • Weil der Thread ja dann nicht beendet ist. Denn er sendet ja eine Nachricht.
    Per Event habe ich irgendwann mal vor Ewigkeiten in der MSDN gefunden. Das ist meiner Meinung nach der offizielle Weg um auf das Ende eines Threads zu warten.

    Macht ja auch Sinn, das das System allen die es interessiert mitteilt das der Thread nicht mehr läuft, und zwar garnicht mehr läuft.

    Das geht auch mit Prozess-Handles, wenn mich nicht alles täuscht. Aber wie gesagt schon lange her.



  • @_Stephan_: nix pollen! Weißt du nicht, was ein Ereignis ist? Man registriert sich bei dem Ereignis und die GUI läuft weiter. Und wenn das Ereignis eintritt, dann wird die Ereignismethode aufgerufen (im Falle von OnTerminate sogar schon direkt im GUI-Thread) und du kannst die entsprechende Reaktion darauf ausführen.



  • @simbad: Weil auch bei WaitForSingle... und WaitForMultiple... eben gewartet wird. So etwas setze ich nicht innerhalb des Hauptprozesses ein, sondern nur in Threads selbst. WaitFor... wartet und genau das ist das Problem. Wenn Du den Timeout kürzer setzt, musst Du das in eine Schleife packen und das unschöne Application->ProzessMessages verwenden.

    Also Nachricht aus dem Thread an den Hauptprozess senden, wenn die Arbeit erledigt ist. Dann kann man aus dem Hauptprozess heraus die Ergebnisse abholen und den Thread anschließend beenden (und das kann durchaus mit Events oder Semaphores realisiert werden).

    Oder aber TThread verwenden...



  • Million Voices schrieb:

    Oder aber TThread verwenden...

    Das würde ich auch sagen, weil es viel einfacher und weniger fehleranfällig ist, als wenn du die RTL von Hand nachbaust.

    Du brauchst TThread::Synchronize() oder TThread::Queue() . Hier ein Beispiel in C++.

    (Mittlerweile habe ich mich so an async / await und Task.Run() gewöhnt, daß mir das manuelle Handhaben von TThread arg umständlich vorkommt. Seit XE7 gibt es ja die Parallel Programming Library, die wie die meisten Innovationen neuerer Zeit sichtlich dem .NET-Framework nachempfunden ist; wenn die schon stabil läuft, wäre sie vielleicht einen Blick wert.)



  • Hallo zusammen,

    erst mal vielen Dank für die vielen Antworten.
    Ich habe das ganze nun in einen TThread ausgelagert welcher auch wie gewünscht seine Arbeit tut.

    Allerdings habe ich nun immer noch das Problem, daß ich im GUI Thread abhänhgig vom Threadzustand ein "Ereignis" auslösen will.

    Th69 schrieb:

    ... Man registriert sich bei dem Ereignis und die GUI läuft weiter. Und wenn das Ereignis eintritt, dann wird die Ereignismethode aufgerufen...

    Ich denke, daß hier z.B. TEvent gemeint ist, allerdings wo ist die Ereignismethode welche den Code ausführt (soll in der Mainform Klasse sein)?
    Ich kann das Event zwar Abfragen, jedoch dann wäre ich ja wieder beim pollen!?

    Irgendwie steh ich glaub gerade auf dem Schlauch, sorry.

    MfG Stephan



  • Stephan schrieb:

    Th69 schrieb:

    ... Man registriert sich bei dem Ereignis und die GUI läuft weiter. Und wenn das Ereignis eintritt, dann wird die Ereignismethode aufgerufen...

    Ich denke, daß hier z.B. TEvent gemeint ist

    Nein, ist es nicht, sondern OnTerminate . Th69 hat sogar darauf verlinkt.

    Außerdem habe ich dich auf zwei Methoden von TThread verwiesen und sogar ein vollständiges Beispiel in C++ verlinkt, das zeigt, wie man sie benutzt. Willst du dir das nicht mal anschauen?



  • @audacia

    Ich habe mir das ganze angeschaut und auch verwendet.
    Vermutlich habe ich mich nicht richtig ausgedrückt, denn mein Problem hat sich etwas geändert.
    Ich muß/will nicht mehr auf das Ende des Threads warten, sondern der Thread soll ein Ereignis im GUI Thread auslösen. Du wirst jetzt berechtigterweise sagen, verwende das von Dir verlinkte, jedoch dies setzt voraus, daß der Code in der Threadklasse implementiert ist.
    Da ich jedoch auf mehrere GUI Element zugreifen muß hätte ich den Code gerne in der GUI Klasse (TForm) implementiert und aus dem TThread im GUI Thread Kontext ausgeführt.
    Ich hoffe es ist nun klarer was ich will, jedoch ich vermute daß das ganze nicht wirklich funktioniert.

    MfG Stephan



  • Und genau dafür ist die Synchronize()-Funktion. Löse einfach dadrin ein Ereignis aus und in der entsprechenden Form-Funktion (welche sich auf dieses Ereignis reistriert hat), führst du die GUI-Aktionen aus...



  • Th69 schrieb:

    Und genau dafür ist die Synchronize()-Funktion. Löse einfach dadrin ein Ereignis aus und in der entsprechenden Form-Funktion (welche sich auf dieses Ereignis reistriert hat), führst du die GUI-Aktionen aus...

    Und falls dich das Wort "Ereignis" verwirrt: schau dir im verlinkten Beispiel die Funktion AddNewNumberToMemo() an, die an Synchronize() übergeben wird. Die greift, wie du siehst, aufs GUI zu. Ebendiese Funktion könnte ja auch in deiner Formklasse stehen, wenn dir das lieber ist. Dann mußt du dir nur noch einen Weg überlegen, wie du die Form-Instanz in den Thread hineinbekommst.

    (ɹǝʇǝɯɐɹɐdɹoʇʞnɹʇsuoʞ slɐ :ʇɹoʍʇu∀)



  • @audacia: das halte ich für keine gute Lösung, daß der Thread die Form kennt (daher ja auch mein Vorschlag mit dem Ereignis 😉

    Hier ein Beispiel:

    // in TThread-Klasse
    public:
        typedef void (__closure *TMyEvent)(); // <- evtl. noch Parameter hinzufügen
    
        TMyEvent OnAction;
    
    // in Execute
    Synchronize(CallActionEvent); // <- hier bin ich mir über die Syntax nicht eindeutig mehr sicher
    
    // Methode
    void CallActionEvent()
    {
      if (OnAction)
          OnAction();
    }
    

    Und dann von der Form-Klasse aus den Thread erzeugen und das Ereignis registrieren:

    thread->OnAction = DoAction;
    
    void MyForm::DoAction()
    {
      // hier der Zugriff auf die GUI
    }
    

    Und bei zusätzlichen Ereignis-Parametern entsprechend die Ausdrücke in den leeren Klammern anpassen.



  • Th69 schrieb:

    @audacia: das halte ich für keine gute Lösung, daß der Thread die Form kennt (daher ja auch mein Vorschlag mit dem Ereignis 😉

    Klar, mit Callback-Funktion ist es besser, und idealerweise sind GUI und Logik sowieso entkoppelt. Ich wollte nur erstmal den Erkenntnisprozeß ins Rollen bringen 🙂

    Wenn man das schon mit einem gebundenen Callback macht, kann man den Weg auch noch weitergehen:

    • das läßt sich offensichtlich verallgemeinern zu einer Hilfsfunktion, die zwei Callbacks annimmt: die eigentliche Aktion (die Execute() -Methode) und eine Benachrichtigungsroutine (dein Callback halt), die auf dem UI-Thread ausgeführt wird
    • dann ist natürlich die Frage, was passiert, wenn die Aktion eine Exception wirft; die sollte vielleicht an den Benachrichtigungs-Callback weitergegeben werden
    • außerdem ist es ineffizient, für jede asynchrone Aufgabe einen Thread zu starten, weil Threads vergleichsweise teuer sind; also könnte man entweder einen Threadpool oder sowas wie QueueUserAPC() verwenden
    • später stellt man dann fest, daß es ganz nützlich wäre, wenn man mehrere Callbackfunktionen angeben könnte, die aufgerufen werden, wenn die Aktion erfolgreich war

    Kurzum, langfristig baut man damit die TPL nach. Deshalb auch mein Verweis auf die Parallel Programming Library; wenn du es richtig machen willst, dann spar dir das Neuerfinden des Rads und nimm die.



  • Hallo zsuammen,

    nochmals vielen dank für die Ideen und die angeregte Diskussion.

    audacia schrieb:

    Dann mußt du dir nur noch einen Weg überlegen, wie du die Form-Instanz in den Thread hineinbekommst.
    (ɹǝʇǝɯɐɹɐdɹoʇʞnɹʇsuoʞ slɐ :ʇɹoʍʇu∀)

    Das ist im Prinzip was ich teilweise jetzt schon machen, ist mir jedoch "zu aufwendig", da ich auf viele verschiedene GUI Elemente zugreifen muß.
    Außerdem ist das ganze noch eine Basisklasse die in vielen verschiedenen Anwendungen weiter vererbt wird, deshalb ist mir hier der Aufwand eigentlich zu groß.

    Th69 schrieb:

    @audacia: das halte ich für keine gute Lösung, daß der Thread die Form kennt (daher ja auch mein Vorschlag mit dem Ereignis 😉

    Irgendwie hat mich die Bezeichnung Ereignis auf den falschen "Weg" geführt, das damit im Endeffekt ein Callback gemeint war, darauf bin ich komischerweise nicht gekommen 😢

    Ich denke ich werde dies zuerst mal mit einem Callback lösen und mir langfristig die von audacia angesprochenen Punkte genauer anschauen.

    MfG Stephan



  • Vllt. bin ich schon zu C# verwöhnt, daß ich dazu Ereignis (anstatt Callback) sage. 😉



  • Th69 schrieb:

    Vllt. bin ich schon zu C# verwöhnt, daß ich dazu Ereignis (anstatt Callback) sage. 😉

    Da wäre Delegate naheliegender 😉


Anmelden zum Antworten