Message Queue: Gui + Worker-Thread



  • Hallo,

    ich habe eine SDI Anwendung (Gui-Thread) mit einem Worker-Thread. Soweit kein Problem.

    Kurzbeschreibung
    - Worker sendet "neue Daten" (PostMessage)
    - Gui-Thread erhält Nachricht (und Zeichnet)
    - Gui-Thread verarbeitet keine anderen Nachrichten mehr (ausser der Worker-Nachrichten)
    - Zugriff auf die Daten mit CriticalSection synchronisiert
    - Gui- und Worker-Thread laufen noch (ein log File wird geschrieben)
    - ein Deadlock ist auszuschliessen (Gui-Thread und Worker-Thread schreiben weiter ihr log File)

    Frage:
    kann es passieren, dass der Worker-Thread schneller Nachrichten sendet als der Gui-Thread verarbeitet?
    Falls ja: gibt es eine Lösung (wie verhindere ich das)?

    Oder sollte ich das anders aufbauen?
    Wie würdet ihr sowas aufbauen?

    Ausführlich
    Der Worker-Thread liest in einer Endlos-Schleife Daten von einem externen Gerät. Hat er die Daten ausgelesen so sendet er eine Nachricht an den Gui-Thread via PostMessage.
    Erhält der Gui-Thread diese Nachricht, so tut er etwas mit den Daten (er stellt sie graphisch dar).
    Der Zugriff auf die Daten wird über ein gemeinsames CriticalSection Objekt/Handle synchronisiert.

    Soweit ist das immer noch kein Problem 🙂
    Aber:
    Es kann passieren, dass die Gui einfriert (nicht der Gui-Thread, erschreibt weiter in ein log)...
    Ich habe den Eindruck, dass der Worker Thread nun schneller Nachrichten sendet als der Gui-Thread verarbeiten kann.
    Der Gui-Thread ist dann nur noch mit dem Verarbeiten dieser Nachrichten beschäftigt (und kümmert sich nicht mehr um Windows-Nachrichten).

    Das Problem tritt nicht auf, wenn der Worker-Thread nach dem PostMessage pausiert (probiert mit Sleep).

    Ist alles etwas kompliziert beschrieben glaub ich... ich hoffe das versteht jemand 😉



  • Um deine Frage zu beantworten, ja. Die Messages werden ja in die Loop gestellt und nacheinander abgearbeitet. Probier mal statt PostMessage SendMessage. Da kann es allerdings passsieren, dass das Gesamtgebilde (GUI- und Workerthread) langsam werden können.



  • SendMessage wär eine Idee.

    Gibt es denn andere Möglichkeiten?
    Kann ich irgendwie herausfinden, ob der Gui Thread alle Nachrichten aus der Message Queue verarbeitet hat (bzw. warten bis er das getan hat)?



  • Mit SendMessage warteste doch so lange bis deine Message vom Empfänger verarbeitet wurde, da Du ja einen Rückgabe-Parameter hast.
    Außerdem kommt es darauf an ob jetzt dein Workerthread Daten Empfängt und so nicht auf die GUI warten kann oder ob der Workerthread die daten irgendwo abholt. Eine andere Möglichkeit ist, das der Workerthread die daten in eine Liste oder so schreibt ud die GUI nach einer definierten Zeit die Daten Visualisiert. Also wenn es nicht Notwendig ist jeden neuen Wert darzustellen/anzuzeigen, dann könnte die Aktualisierung über einen Timer geschehen. Ist aber ebend alles eraten, da alles drauf ankommt wie die Daten in die Applikation kommt und wie hoch die Aktualisierungsrate sein muß.

    Gruß Matthias



  • ihoernchen schrieb:

    Kann ich irgendwie herausfinden, ob der Gui Thread alle Nachrichten aus der Message Queue verarbeitet hat (bzw. warten bis er das getan hat)?

    Wenn es OK ist dass der Workerthread angehalten wird bis die GUI aktualisiert wurde, dann kannst du ohne weiteres SendMessage verwenden.
    Wenn der Workerthread dagegen so schnell wie möglich Daten abholen soll (z.B. weil er die in ein Logfile schreibt, oder verarbeitet und sonstwas damit macht) könntest du ein sowas in der Art machen:

    CCriticalSection g_cs;
    Data g_data;
    bool g_newDataForGUI = false;
    bool g_endThreadFlag = false;
    
    void WorkerThreadFunction()
    {
        for (;;)
        {
            Data d = ReadData();
    
            CSingleLock lock(&g_dataCs, TRUE);
            g_data = d;
            if (!g_newDataForGUI)
            {
                g_newDataForGUI = true;
                PostMessage(...);
            }
    
            if (g_endThreadFlag)
                break;
    
            // CSingleLock verlässt hier den Scope und gibt damit die CRITICAL_SECTION wieder frei
        }
    }
    
    void GUI::OnTimer(int id)
    {
         if (id == ...) // langsamer timer, sagen wir jede sekunde (dient nur als "notnagel")
             UpdateGUI();
    }
    
    void GUI::OnXYZMessage() // die message von unserem worker thread
    {
        UpdateGUI();
    }
    
    void GUI::UpdateGUI()
    {
        Data d;
        {
            CSingleLock lock(&g_dataCs, TRUE);
            if (!g_newDataForGUI)
                return;
    
            d = g_data;
            g_newDataForGUI = false;
        } // das CSingleLock lassen wir hier sterben, damit der Thread nicht zu lange blockiert wird
    
        // "d" anzeigen...
    }
    

    Der zusätzliche Timer der auch verwendet wird um UpdateGUI aufzurufen ist im Prinzip nur für den Fall da, dass die Message Queue schon voll war (warum auch immer) zu dem Zeitpunkt wo der Thread PostMessage ausführt. In diesem (extrem unwahrscheinlichen) Fall könnte es dann theoretisch passieren dass der Thread eine Message posten will, diese aber verworfen wird (weil Queue voll - Limit ist per Default irgendwo bei 10000 Messages), und daher der GUI Thread nie über den OnXYZMessage-Handler die UpdateGUI Funktion ausführt. Da aber g_newDataForGUI dann schon gesetzt ist, würde kein weiterer Versuch mit PostMessage mehr unternommen, und es würde die GUI nie mehr aktualisiert (auch nachdem sich die Queue wieder geleert hätte).

    Praktisch wird der Fall wahrscheinlich nie auftreten, allerdings könnte man ohne diesen "Notnagel" nicht behaupten dass das Programm 100% wasserdicht ist.

    Testen liesse sich das Verhalten des Programmes in diesem Extremfall z.B. indem man ein "if ((rand() % 100) == 42)" vor das "PostMessage(...)" setzt.

    p.S.: du solltest natürlich keine globalen Variablen verwenden wo es nicht sein muss (gib dem Worker-Thread nen Zeiger auf eine Context-Struktur oder sowas), bloss für so ein Beispiel ist es halt einfacher, weil schneller zu tippen 😉


  • Mod

    Ich tendiere immer eher zu einer Timerlösung. Die eben alle 250msec nachsieht ob ein neues Ergebnis da ist. Dann spart man sich ein PostMessage aus dem Thread.



  • Eine Timerlösung wollte ich nicht. Entweder die Gui ist dann zu schnell oder zu langsam. Schöner ist es schon, wenn die Gui erst was tut sobald Arbeit vorhanden ist.

    @hustbaer
    Danke für den Post mit dem schönen Beispiel. Ich habs nun, denke ich, hinbekommen.
    Zwar ohne Timer. Trotzdem hat mir dein Beispiel weitergeholfen. Das alles mal so "roh" zu sehen war notwendig 😉

    Problem war die Gui hatte auch noch Arbeit zu tun (ausser dem Anzeigen). Was eigentlich quatsch ist (das soll ja der Worker machen...).

    Das Problem bleibt natürlich bestehen - sollte die Gui mal viel zu tun haben.
    Deshalb komm ich letztendlich wohl nicht um eine Lösung mit Timer rum. Naja...


Anmelden zum Antworten