CCriticalSection (Mutex) funktioniert nicht innerhalb einer Nachrichtenschleife



  • Hallo!

    Ich habe Schwierigkeiten mit einem CCriticalSection-Objekt, das ich bisher schon oft und ohne Probleme eingesetzt habe. Dieses Mal habe ich jedoch eine Konstruktion, in der solch ein Synchronisierungsobjekt nicht funktioniert. Vielleicht habt ihr eine Idee:

    Und zwar läuft zusätzlich zum Haupt-Thread ein Neben-Thread, der den Haupt-Thread informiert, sobald ein bestimmter Dialog angezeigt werden soll. Da dieser Dialog modal erscheinen und den Neben-Thread nicht blockieren soll, schickt der Neben-Thread eine entsprechende Nachricht an das Hauptfenster im Haupt-Thread. Der Haupt-Dialog der Anwendung fängt diese Nachricht in seiner PreTranslateMessage(MSG* pMsg)-Methode ab. Handelt es sich um eine entsprechende Nachricht vom Neben-Thread wird noch in der PreTranslateMessage-Methode der anzuzeigende Dialog modal geöffnet:

    BOOL MainDlg::PreTranslateMessage(MSG* pMsg) {
      if (pMsg->message == WM_NEBEN_THREAD_MSG) {
        NebenDlg dlg;
        dlg.DoModal();
        return TRUE;
      }
      return CDialog::PreTranslateMessage(pMsg);
    }
    

    Soweit funktioniert das sehr gut. Nun kommt aber die zuätzliche Anforderungen hinzu, dass der Neben-Dialog nicht erneut sofort geöffnet werden soll, wenn ein Neben-Dialog bereits angezeigt wird. Stattdessen soll in diesem solange gewartet werden, bis der vorherige Dialog geschlossen ist. Dazu eignet sich natürlich ein Mutex am besten, wobei ich hier die CCriticalSection aufgrund ihrer einfachen Handhabung bevorzuge. Mit dieser neuen Anforderung sieht der Code also anschließend wie folgt aus:

    CCriticalSection _globalLock; // Nur eine globale Instanz!
    
    BOOL MainDlg::PreTranslateMessage(MSG* pMsg) {
      if (pMsg->message == WM_NEBEN_THREAD_MSG) {
        if (_globalLock.Lock()) {
          NebenDlg dlg;
          dlg.DoModal();
          _globalLock.Unlock();
        }
        return TRUE;
      }
      return CDialog::PreTranslateMessage(pMsg);
    }
    

    Dieser Code sollte eigentlich die Anforderungen erfüllen. Aber nun kommt's: Dieser Lock kann mehrfalls gelockt werden! Obwohl es sich um die selbe Instanz handelt, habe ich mittels Debugging festgestellt, dass der mehrmalige Lock()-Aufruf sofort ausgeführt wird, obwohl bisher noch kein modaler Neben-Dialog geschlossen wurde und somit Unlock() auch noch nicht aufgerufen wurde.
    Da ich bisher noch keine Probleme mit der CCriticalSection hatte, vermute ich, dass eine Verwendung in einer Nachrichtenverarbeitungsschleife (hier PreTranslateMessage) nicht problemlos möglich ist. Beim nächsten Aufruf von Lock() weiß die CCriticalSection dann einfach noch nicht, dass sie bereits gelockt wurde. Das ist meine Vermutung; jedenfalls besteht das Problem nun noch. Hat jemand eine Ahnung, woran das liegt und was ich das Problem lösen kann?

    Beste Grüße!
    johlke



  • Falls es von Bedeutung ist: Es handelt sich um eine MFC-Anwendung für die PocktPC-Plattform.



  • Wo hast Du denn hier ein "Mutex"???
    "CCriticalSection" golt *nicht* über Prozessgrenzen hinweg...



  • Hallo Jochen,
    beide Threads laufen in der selben Anwendung, CCriticalSection scheint also geeignet. "Mutex" ist ein allgemeiner Oberbegriff für die Synchronisierung von nebenläufigen Threads/Prozessen: http://de.wikipedia.org/wiki/Mutex

    Gruße
    johlke



  • Jochen meinte hier nicht den "Oberbegriff" Mutex, sondern tatsächlich den eigentlichen Mutex, z.B. mit der WinAPI-Funktion CreateMutex() erzeugt.

    Aber da Deine Threads im selben Prozess (und damit Adressraum) sind, ist ein Mutex eigentlich "oversized" (da Kernel-Objekt!).
    Critical section ist schon die richtige Wahl (und natürlich schlanker als ein Mutex).

    johlke schrieb:

    Nun kommt aber die zuätzliche Anforderungen hinzu, dass der Neben-Dialog nicht erneut sofort geöffnet werden soll, wenn ein Neben-Dialog bereits angezeigt wird. Stattdessen soll in diesem solange gewartet werden, bis der vorherige Dialog geschlossen ist. Dazu eignet sich natürlich ein Mutex am besten, wobei ich hier die CCriticalSection aufgrund ihrer einfachen Handhabung bevorzuge.

    Kann sich der Haupt-Thread nicht mit einer simplen Bit-Variable merken ob ein dialog geöffnet ist oder nicht?
    Nur mal so als Idee, manchmal sind die einfacheren Lösungen die besten.

    Martin





  • Hallo Mmacher,

    ich habe für Prozesssynchronisierungen ursprünglich den Oberbegriff "Mutex" kennengelernt. Wie du schreibst, gibt es aber auch die Methode CreateMutex(), die einen Mutex erzeugt, das ist dann schon was konkretes, an das wohl Jochen gedacht hat.

    Ja, inzwischen hab ich auch gemerkt, dass die Verwendung eines CCriticalSection innerhalb des selben Threads blödsinnig ist. Ich hatte mir ursprünglich nämlich davon erhofft, dass beim Lock()-Aufruf solange gewartet wird, bis der erste Neben-Dialog geschlossen ist. Dann sollte die zweite Instanz des Neben-Dialogs erscheinen. Macht aber kein Sinn, wie soll ein Thread auf sich selbst warten? Klar, dass Lock() dann immer funktioniert, auch wenn vorher kein Unlock() erfolgt ist. Sonst hätte ich ja ein Dead-Lock 😉

    Anscheinend lassen sich meine Anforderungen dann gar nicht anders realisieren als ich es jetzt gemacht habe: Ich habe eben eine boolesche Variable benutzt (wie du auch schon vorgeshclagen hast), die anzeigt, ob eine Instanz des Dialoges gerade angezeigt wird. Ist dies der Fall starte ich jetzt einen Thread, der 1 Sekunde warten und dann schaut, ob der vorherige Neben-Dialog inzwischen geschlossen wurde. Ist dies der Fall, kann der nächste angezeigt werden, andererseits wird nochmal eine Sekunde gewartet. Allerdings muss jedes Mal die Codeausführung zur Anzeige des Dialogs im Haupt-Thread erfolgen, da ansonsten der Haupt-Thread einfach sein Ding weiter macht und den Neben-Dialog gar nicht beachtet. So kann es nämlich sein, dass der Haupt-Thread eigene Dialoge anzeigt und diese den Neben-Dialog überlagert. Ist alles in allem ganz schön kompliziert zu realisieren, aber mir fällt keine bessere Lösung ein.



  • Eine einfache Variable ist nicht Threadsafe.

    Was ist das Problem mit CCriticalSection?
    Folgendes sollte doch gehen:

    CCriticalSection s;
    // init s 
    MyThread thread(&s);
    
    ...
    s.Lock();
    ... 
    s.Unlock();
    
    // Klasse für den Thread
    void Thread::TueIrgendwas()
    {
      sFromMainThread->Lock();
    ...
      sFromMainThread->Unlock();
    }
    


  • Hallo ihoernchen,

    ich habe die CCriticalSection im selben Thread benutzt, deshalb funktioniert jeder weitere Lock()-Aufruf obwohl vorher noch keine Unlock() erfolgt ist. Der Thread würde somit auf sich selbst warten und das wird anscheinend verhindert.

    Danke und Gruß!



  • johlke (nicht eingeloggt) schrieb:

    Hallo ihoernchen,

    ich habe die CCriticalSection im selben Thread benutzt, deshalb funktioniert jeder weitere Lock()-Aufruf obwohl vorher noch keine Unlock() erfolgt ist. Der Thread würde somit auf sich selbst warten und das wird anscheinend verhindert.

    Danke und Gruß!

    Ja, logisch. Die CS sind nämlich reentrant.
    Mach ein Flag, verwende die InterlockedXXX Funktionen fürs setzen und abfragen.
    Alternativ kannst Du auch ein Event (Manual Reset) verwenden.

    BTW: CS sind da um den Zugriff von verschiedenen Threads zu synchronisieren.

    Simon


Anmelden zum Antworten