std::threads in bestimmter Reihenfolge?



  • Hallo,

    ich habe momentan folgendes Problem: Eine GUI über die der Benutzer bestimmte, ziemlich teuere Berechnungen starten kann, soll während dieser Berechnungen nicht "einfrieren". Soweit so gut.

    Dazu kommt jetzt allerdings dass die durch den Nutzer gestarteten Berechnung in Reihenfolge erledigt werden sollen - sprich zuerst die älteste Berechnung, dann die nächste, und zuletzt die am letzten gestartete Berechnung. Also eigentlich sequentiell, aber trotzdem (insgesamt) parallel zu meiner GUI.

    Wie würde man sowas am besten lösen? Einfach im GUI thread die aktuell laufende Berechnung joinen kann ich ja nicht weil die GUI dann wieder lagt...



  • Mit einem GUI- und einem Workerthread (der alle Berechnungen ausführt).



  • Mit einer Queue, in die der GUI Thread die durchzuführenden Berechnungen reinpushed und einem Worker-Thread der sie dort rauspopped und abarbeitet... 😉

    Am einfachsten nimmst du eine std::queue und sicherst das Pushen und Poppen über ein std::mutex + std::condition_variable ab.



  • Die GUI kann natürlich nicht einfach anhalten, bis die Berechnung beendet sind.

    Der Hintergrund-Thread sollte in irgendeiner Weise ein Flag setzen (ein bool würde reichen), so dass das die GUI mitbekommt.

    Die GUI ihrerseits startet einen Timer (ca. 100 ms, kommt nicht so darauf an) in dessen Aufrufen auf das Flag reagiert wird. Der Vorteil des Timers ist, dass man auch einen Fortschrittsbalken aktualisiern kann.



  • coder777 schrieb:

    Die GUI kann natürlich nicht einfach anhalten, bis die Berechnung beendet sind.

    Der Hintergrund-Thread sollte in irgendeiner Weise ein Flag setzen (ein bool würde reichen), so dass das die GUI mitbekommt.

    Die GUI ihrerseits startet einen Timer (ca. 100 ms, kommt nicht so darauf an) in dessen Aufrufen auf das Flag reagiert wird. Der Vorteil des Timers ist, dass man auch einen Fortschrittsbalken aktualisiern kann.

    Wieso pollen? Es gibt bestimmt in praktisch jedem GUI Toolkit (na es gibt bestimmt auch Gegenbeispiele) ein Eventmodell, wo ich auch aus einem anderen Thread ein Event generieren kann, welches signalisiert, dass die Berechnung zu Ende ist. Das wird dann ganz regulär in der Eventloop der GUI verarbeitet.



  • tntnet schrieb:

    Wieso pollen? Es gibt bestimmt in praktisch jedem GUI Toolkit (na es gibt bestimmt auch Gegenbeispiele) ein Eventmodell, wo ich auch aus einem anderen Thread ein Event generieren kann, welches signalisiert, dass die Berechnung zu Ende ist. Das wird dann ganz regulär in der Eventloop der GUI verarbeitet.

    +1
    Ich bin zwar nicht so allergisch gegen Polling wie viele andere Programmierer, aber in diesem Fall sehe ich auch keinen Grund dafür.
    Und wenn es keinen guten Grund gibt etwas zu pollen, dann überwiegen natürlich die guten Gründe dagegen (schlechte Performance, Delay bis man den Event mitbekommt, Verhinderung von Stromsparfeatures etc.).



  • Hallo,

    danke schonmal für die Antworten. Dann werd ich mich jetzt mal mit conditional_variable auseinander setzen, sah mir bis jetzt immer zu kompliziert aus, aber muss wohl jetzt sein 🙂

    Vielleicht noch als Hintergrund, ich brauche das halt für folgende Anwendung:

    Ich hab eine Textbox die ich mit einer anderen Textbox (aus einem anderen, fremden Prozess, z.b. Notepad) "synchronisiere". Das heißt wenn ich etwas eintippe soll der Text direkt in die fremde Textbox geschrieben werden.

    Dafür benutze ich SendMessage und WM_SETTEXT, schon seit einiger Zeit und das funktioniert. Leider ist das Ganze sehr langsam wenn der Inhalt der Textbox sehr lang wird (SendMessage ist ja synchron und kann auch nicht abgebrochen werden).

    Deswegen hab ich das (vom Prinzip her) in einen thread ausgelagert, ungefähr so:

    void send_text_to_external_app(std::string text)
    {
        SendMessage(external_window_handle, WM_SETTEXT, 0, (LPARAM)text.c_str());
    }
    
    void on_text_changed()
    {
        std::string text = get_textbox_text();
    
        std::thread t(send_text_to_external_app, text);
        t.detach();
    }
    

    So hab ich das vor kurzem getestet und das geruckel der GUI bei langen Texten ist weg - man kann wunderbar flüssig tippen und der Text wird trotzdem (in meinen Tests) korrekt übertragen.

    Meine Angst ist halt jetzt dass mal ein aktuellerer Sende-Auftrag zu langsam ist, so dass am Ende nicht der aktuellste Text in der Textbox steht sondern ein älterer, also die Textbox nicht mehr 100% zuverlässig synchron ist.

    Berechtigte Angst? Bis jetzt (2 Tage testen) hats imer geklappt aber ich hab ein ungutes Gefühl dabei. Und ist die Lösung die threads quasi hintereinander zu queuen in diesem Kontext sinnvoll oder gibts für so eine Anwendung noch etwas einfacheres/besseres?



  • Wieso pollen?

    Wie gesagt: Um den Fortschrittsbalken zu aktualisieren.



  • coder777 schrieb:

    Wieso pollen?

    Wie gesagt: Um den Fortschrittsbalken zu aktualisieren.

    Das sind 2 unterschiedliche Aufgaben. Zum einen einen Fortschrittsbalken anzuzeigen und zum anderen auf den Abschluss einer Aufgabe reagieren. Die sind sicher nicht ganz unabhängig.

    Ein Event ist die richtige Lösung. Und darauf zu verzichten und das auch noch zu begründen, dass man damit ja einen Fortschrittsbalken aktualisieren kann ist doch ein wenig bequem 😉 .



  • Wie gesagt: Um den Fortschrittsbalken zu aktualisieren.

    Eigentlich sind doch die Zeiten wo das bequemer (für den Programmierer) war vorbei oder ? Heutzutage ist doch nen Threadwechsel via Events(die man eh hat) nen einzeiler (Dank boost, Qt und co)^^

    Dafür benutze ich SendMessage und WM_SETTEXT, schon seit einiger Zeit und das funktioniert.

    Versteh ich das richtig ? SendMessage ist die Winapi funktion ? und du holst Dir das Windowshandle eines anderen Prozesses ? und schickst dem Text ?
    Oder steh ich aufm Schlauch ?

    Berechtigte Angst? Bis jetzt (2 Tage testen) hats imer geklappt aber ich hab ein ungutes Gefühl dabei. Und ist die Lösung die threads quasi hintereinander zu queuen in diesem Kontext sinnvoll oder gibts für so eine Anwendung noch etwas einfacheres/besseres?

    jedesmal wenn du tippst(das textchange bekommst), machst du einen neuen thread auf, parametrierst den und "schickst den ab" ...
    Theorethisch kann es passieren, das sich die textchanges überholen .... aber praktisch isses eher unwahrscheinlich.
    Was mir mehr Sorgen machen wuerde ist die Performance / ressourcenverbrauch ^^
    Bei jeden tippen nen thread ist ...aehm ... sehr verschwenderisch^^ WIe oft bekommst du beim tipen so nen textchange? nach jedem Buchstaben ?
    Besser nur einen thread, den text in ne queue und per event informieren, der thread holt sich den text raus und arbeit es ab (SendMessage) ...

    Ciao ...



  • tntnet schrieb:

    Das sind 2 unterschiedliche Aufgaben. Zum einen einen Fortschrittsbalken anzuzeigen und zum anderen auf den Abschluss einer Aufgabe reagieren. Die sind sicher nicht ganz unabhängig.

    Ich sehe keinen kein Problem darin 2 Aufgaben in einer Funktion zu lösen, da die noch nicht einmal unabhängig sind.

    tntnet schrieb:

    Ein Event ist die richtige Lösung. Und darauf zu verzichten und das auch noch zu begründen, dass man damit ja einen Fortschrittsbalken aktualisieren kann ist doch ein wenig bequem 😉 .

    Sehe ich nicht so.
    1. Entkopplung:
    - Der Hintergrund-Thread hat keine Refernzen ins GUI (kann dementsprechen auch ohne GUI oder in einer Konsolenumgebung laufen).
    - Egal was der Hintergrund-Thread macht, das GUI bleibt ansprechbar.

    2. Ereignissteuerung kann schnell kompliziert werden, besonders im Fehlerfall.

    3. Wenn etwas im Hintergrund passiert, sollte es sich in jedem Fall in der GUI wiederspiegeln.

    Ja, die Timer/Thread Lösung ist einfach (und stabil), deswegen setze ich sie immer ein, es sei denn es spricht irgend etwas dagegen.

    Eigentlich sind doch die Zeiten wo das bequemer (für den Programmierer) war vorbei oder ? Heutzutage ist doch nen Threadwechsel via Events(die man eh hat) nen einzeiler (Dank boost, Qt und co)^^

    Asynchron kommunikation ist nie einfach. Es gibt im Wesentlichen zwei Probleme:

    1. Je mehr gewartet werden muss, desto langsamer wird es bis hin zur völligen Serialisierung, so dass der Thread keinen Sinn mehr macht (man merkt das womöglich nicht).

    2. Deadlock: Je mehr hin und her geht desto wahrscheinlich tritt einer auf.

    Ob Einzeiler oder nicht is relativ nebensächlich.



  • RHBaum schrieb:

    Versteh ich das richtig ? SendMessage ist die Winapi funktion ? und du holst Dir das Windowshandle eines anderen Prozesses ? und schickst dem Text ?

    Korrekt, genau so läufts 🙂

    RHBaum schrieb:

    jedesmal wenn du tippst(das textchange bekommst), machst du einen neuen thread auf, parametrierst den und "schickst den ab" ...
    Theorethisch kann es passieren, das sich die textchanges überholen .... aber praktisch isses eher unwahrscheinlich.
    Was mir mehr Sorgen machen wuerde ist die Performance / ressourcenverbrauch ^^
    Bei jeden tippen nen thread ist ...aehm ... sehr verschwenderisch^^ WIe oft bekommst du beim tipen so nen textchange? nach jedem Buchstaben ?

    Genau, nach jedem Buchstaben beim Tippen.

    Also dass das teuer ist stimmt wohl schon, aber seit dem auslagern in einen Hintergrund thread läuft die Textbox wunderbar flüssig auch bei sehr langen Texten und bei sehr schnellem tippen. Davor (ohne thread) hat es bei längeren Texten geruckelt (wahrscheinlich weil erstmal der ganze string in den Kontext des anderen Prozesses geschoben werden muss?).

    Problem ist halt dass ich auf die externe Anwendung auch keinen Zugriff habe (ist kein Sourcecode oder ähnliches verfügbar), deswegen bleibt mir nichts anderes übrig als das so zu "hacken".

    RHBaum schrieb:

    Besser nur einen thread, den text in ne queue und per event informieren, der thread holt sich den text raus und arbeit es ab (SendMessage) ...

    Hm, also eine std::queue<std::string> für den Text? Und bei jedem Textchanged dann einen Event abfeuern?

    Es muss halt schon auch sichergestellt sein dass ein Text auch wirklich ankommt... also nicht dass zwischen der Abarbeitung der queue der Nutzer dann das Textbox Fenster schließt und der Text dann im Nirvana verschwindet...



  • happystudent

    Ich denke, du kannst die den Thread sparen, wenn du PostMessage(...) -> Asynchron statt SendMessage(...) -> synchron (wartet auf Rückmeldung) verwendest.



  • coder777 schrieb:

    happystudent

    Ich denke, du kannst die den Thread sparen, wenn du PostMessage(...) -> Asynchron statt SendMessage(...) -> synchron (wartet auf Rückmeldung) verwendest.

    Ne, PostMessage geht nicht mit Pointer-Parametern...



  • Ne, PostMessage geht nicht mit Pointer-Parametern...

    Das stimmt.



  • coder777 schrieb:

    Das stimmt.

    Ja, das ist das Problem. Sonst würde ich einfach PostMessage verwenden und fertig. Am liebsten hätte ich einen C++ Wrapper um SendMessage, der PostMessage nachbildet. Falls es sowas schon gibt, immer her damit 🙂

    Darüber hinaus kann ich aber deinen Argumenten nicht folgen:

    coder777 schrieb:

    1. Entkopplung:
    - Der Hintergrund-Thread hat keine Refernzen ins GUI (kann dementsprechen auch ohne GUI oder in einer Konsolenumgebung laufen).
    - Egal was der Hintergrund-Thread macht, das GUI bleibt ansprechbar.

    Das ist bei Events auch gegeben?

    coder777 schrieb:

    3. Wenn etwas im Hintergrund passiert, sollte es sich in jedem Fall in der GUI wiederspiegeln.

    Kann es ja auch mit Events, das schließt sich ja nicht aus?

    coder777 schrieb:

    2. Ereignissteuerung kann schnell kompliziert werden, besonders im Fehlerfall.

    Reine Geschmacksache würde ich sagen. Hab in C# schon viele Event-basierte GUIs gebaut und finde das sehr schön einfach und gut nachvollziehbar.

    coder777 schrieb:

    1. Je mehr gewartet werden muss, desto langsamer wird es bis hin zur völligen Serialisierung, so dass der Thread keinen Sinn mehr macht (man merkt das womöglich nicht).

    Eine Aufgabe dauert so lange wie sie dauert.

    Warum soll das keinen Sinn machen? Wenn ich per GUI eine Gebäudeheizung einstellen will kann das mal 1-2 Tage dauern bis der Sollwert erreicht ist - wie definierst du überhaupt das etwas "zu lange" braucht und deswegen keinen Sinn mehr macht?

    coder777 schrieb:

    2. Deadlock: Je mehr hin und her geht desto wahrscheinlich tritt einer auf.

    Macht mMn auch keinen Sinn... Ein Deadlock sollte durch richtige locks o.ä. ausgeschlossen werden, ansonsten hat man halt ein verbuggtes (= falsches) Program erstellt. Falsch wird dadurch ja nicht besser dass es etwas seltener Abstürzt? Ein häufiger Deadlock ist mir sogar lieber als ein extrem seltener, weil das einfacher zu debuggen ist.



  • @happystudent
    Die Daten steckt man in die Fensterklasse (über Mutex synchronisiert), und dann schickt man per PostMessage nur noch eine "ich hab was in Liste X eingetragen was verarbeitet gehört" Benachrichtigung.

    Mach' ich oft so, funktioniert wunderbar.



  • hustbaer schrieb:

    @happystudent
    Die Daten steckt man in die Fensterklasse (über Mutex synchronisiert), und dann schickt man per PostMessage nur noch eine "ich hab was in Liste X eingetragen was verarbeitet gehört" Benachrichtigung.

    Hm, aber die Fensterklasse (bzw. das Ganze Fenster) gehört ja zu einem externen Prozess... Dann müsste ich ja erstmal in dem fremden Prozess Speicher allozieren und den string dann entsprechend transportieren...

    Den Pointer auf den string könnte ich schon per SendMessage verschicken, aber die Adresse passt dann im Zielprozess halt nicht mehr...

    Geht das überhaupt alles, oder war die Lösung eher für Fenster gedacht die sich alle im gleichen Prozess aufhalten?



  • happystudent schrieb:

    Geht das überhaupt alles, oder war die Lösung eher für Fenster gedacht die sich alle im gleichen Prozess aufhalten?

    Ja, das. Sorry, hatte den gesamten Kontext nicht mehr im Kopf. Bzw. ich bin mir gar nicht sicher ob ich das mit der Teilung in zwei Prozesse überhaupt gelesen habe.

    happystudent schrieb:

    Dafür benutze ich SendMessage und WM_SETTEXT , schon seit einiger Zeit und das funktioniert. Leider ist das Ganze sehr langsam wenn der Inhalt der Textbox sehr lang wird (SendMessage ist ja synchron und kann auch nicht abgebrochen werden).
    (...)
    Berechtigte Angst? Bis jetzt (2 Tage testen) hats imer geklappt aber ich hab ein ungutes Gefühl dabei. Und ist die Lösung die threads quasi hintereinander zu queuen in diesem Kontext sinnvoll oder gibts für so eine Anwendung noch etwas einfacheres/besseres?

    Ich würde da wohl einfach einen eigenen Hilfsthread machen, der so lange am Leben bleibt wie du WM_SETTEXT verschicken willst.
    Der macht dann im Prinzip:

    void HelperThread::NotifyTextChanged(std::string const& text)
    {
        {
            unique_lock lock(m_mutex);
            m_text = text;
            m_textChanged = true;
        }
    
        m_condition.notify_one();
    }
    
    void HelperThread::ThreadFn()
    {
        unique_lock lock(m_mutex);
        while (true)
        {
            m_condition.wait(lock, [&]{ return m_endThread || m_textChanged; });
            if (m_endThread)
                break;
    
            std::string text = m_text;
            m_textChanged = false;  // <---- EDIT
    
            lock.unlock();
            ::SendMessage(m_targetWindowHandle, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(text.c_str()));
            lock.lock();
        }
    }
    

    Das Ruckeln auf der Sender-Seite muss damit auch weg sein, und du hast immer nur einen Thread und schickst die WM_SETTEXT auch garantiert in der richtigen Reihenfolge.
    Und du hast viel weniger Overhead, da du nicht dauernd neue Threads erzeugst und wieder zerstörst.

    Höchstens die Empfängerseite kann noch "ruckeln" - wenn die SendMessage Aufrufe zu lange brauchen.

    Ansonsten kannst du natürlich auch einen Shared-Memory Bereich oder eine Shared Data-Section verwenden wo du den Text reinsteckst. Synchronisiert mit ner named interprocess Mutex (Win32 Mutex oder Boost Interprocess). Und bei Änderung ne Message posten oder nen named EVENT setzen (manual reset Event -- oder auch wieder ne Boost Interprocess Condition Variable).

    EDIT: m_textChanged = false; vergessen - ohne das würde der Thread ja unnötigerweise dauernd laufen.



  • hustbaer schrieb:

    Ich würde da wohl einfach einen eigenen Hilfsthread machen, der so lange am Leben bleibt wie du WM_SETTEXT verschicken willst.
    Der macht dann im Prinzip:

    Funktioniert perfekt, besten Dank 👍

    Damit läuft das einfach viel smoother als jeden SendMessage synchron zu verschicken (und das detachen der threads war mir zu riskant).

    hustbaer schrieb:

    Höchstens die Empfängerseite kann noch "ruckeln" - wenn die SendMessage Aufrufe zu lange brauchen.

    Das stimmt, bei extrem großen Texten kann man das merken wenn man sehr schnell aus dem Textfeld rausklickt. Macht aber gar nix, hauptsache man kann schön schreiben.


Anmelden zum Antworten