Probleme mit Lock-Unlock-Mutex

Anfängerfragen zum Programmieren mit PureBasic.
Benutzeravatar
remi_meier
Beiträge: 1078
Registriert: 29.08.2004 20:11
Wohnort: Schweiz

Beitrag von remi_meier »

@Hugo:
Sowas hier wäre wohl richtig:

Code: Alles auswählen

; enable ThreadSafe! 

EnableExplicit 


Procedure CreateWindow() 
  OpenWindow(0, 200, 200, 500, 600, "Test") 
  CreateGadgetList(WindowID(0)) 
  ButtonGadget(1, 20, 20, 100, 50, "Update") 
  ListIconGadget(2, 20, 90, 460, 490, "List", 200) 
  ; wir werden zum ListIconGadget nen Mutex erstellen: 
  Global LCAccess.l = CreateMutex() 
  
EndProcedure 


; das ist unser Thread! 
Procedure UpdateList(Gadget.l) 
  Protected NewList ll.s() 
  Protected z.l 
  
  ; noch kurz den Update-Button deaktivieren 
  DisableGadget(1, #True) 
  
  ; das ist unser update-prozess... wir werden 
  ; ihn mal schön langsam machen, z. B. Internet- 
  ; zugriff! 
  For z = 0 To Random(5) 
    AddElement(ll()) 
    ll() = "Ne Zahl: " + Str(Random(100)) 
    ; jo! breeemmmsen: 
    Delay(100) 
  Next 
  
  ; aaalso: Wir haben die Daten in ner LL und 
  ; werden nun also das ListIconGadget updaten, bzw. 
  ; hier nur weiter füllen ;) 
  LockMutex(LCAccess) 
  ForEach ll() 
    ; wir Schauen, ob der Eintrag schon existiert, 
    ; wenn nicht, dann hinzufügen :D 
    AddGadgetItem(2, -1, ll()) 
  Next 
  ; jou, fertisch :D 
  UnlockMutex(LCAccess) 
  
  ; und den update-button wieder enablen: 
  DisableGadget(1, #False) 
EndProcedure 




CreateWindow() 

Define.l Event 
Repeat 
  Event = WaitWindowEvent() 
  
  If Event = #PB_Event_Gadget 
    Select EventGadget() 
      Case 1 
        ; Update-Knopf gedrückt 
        ; wir starten den Update-Thread! 
        CreateThread(@UpdateList(), 2) 
        
      Case 2 
        If EventType() = #PB_EventType_LeftClick 
          ; ListIcon-Gadget will was, wir wollen 
          ; also mal exklusiven Zugriff! 
          If TryLockMutex(LCAccess) 
            ; wir dürfen das ListIcon bearbeiten! 
            AddGadgetItem(2, -1, "ich bin geadded!") 
            
            ; wir sind Fertig :D 
            UnlockMutex(LCAccess) 
          EndIf 
        EndIf 
        
    EndSelect 
  EndIf 
  
Until Event = #PB_Event_CloseWindow
@Nic: Freak meinte wohl eher, dass es genau eine Event-Schleife geben
kann, und diese in einem Thread laufen könnte. Du erstellst hier aber
5 Event-Schleifen.
Aber ich bin mir da leider auch nicht so sicher :roll:
Benutzeravatar
remi_meier
Beiträge: 1078
Registriert: 29.08.2004 20:11
Wohnort: Schweiz

Beitrag von remi_meier »

Also, was ich bis jetzt festgestellt habe (muss nicht 100% richtig sein):
- Gadgets + Fenster müssen im gleichen Thread erstellt werden, wie
ihre Event-Schleife
- Es darf nur eine Event-Schleife geben

Man sollte wohl also einen Thread für die gesamte GUI erstellen. Die
Aufgaben, die das Programm erledigen soll und welche von der GUI
gestartet werden sollen, können vom Event-Thread als neuen Thread
gestartet werden.
Ich habe mal ein Beispiel gemacht:

Code: Alles auswählen

Procedure CreateWindow()
  OpenWindow(0, 200, 200, 500, 500, "test")
  CreateGadgetList(WindowID(0))
  ButtonGadget(1, 20, 20, 50, 50, "hallo")
  ButtonGadget(2, 120, 20, 50, 50, "du")
EndProcedure

Procedure Message(*p)
  For z = 0 To 20
    Debug PeekS(*p)
    Delay(100)
  Next
EndProcedure


Procedure EventThread(d.l) 
  CreateWindow()
  Repeat
    event = WaitWindowEvent()
    If event = #PB_Event_Gadget And EventWindow() = 0
      Select EventGadget()
        Case 1
          CreateThread(@Message(), @"Hallo!!")
        Case 2
          CreateThread(@Message(), @"du!!")
      EndSelect
    ElseIf event = #PB_Event_CloseWindow And EventWindow() = 1
      CloseWindow(1)
    EndIf
    
  Until event = #PB_Event_CloseWindow And EventWindow() = 0
  
EndProcedure 


th = CreateThread(@EventThread(), 0)


WaitThread(th)

End
Das, was ich nicht verstehe: Der Debugger merkt hier nicht, dass das
Programm fertig ist! Wieso?
Hugo
Beiträge: 56
Registriert: 30.03.2005 12:07
Wohnort: Nähe München

Beitrag von Hugo »

Nochmals danke für eure Antowrten.

Ich versuche mal mein Problem etwas besser zu beschreiben.

Was ich erreichen möchte ist folgends: Messdaten sollen über z.B. die serielle Schnittstelle eingelesen und grafisch angezeigt werden. Ohne die Messdatenerfassung in einen eigenen Thread zu legen würde die Kommunikation z.B. beim Verschieben des Fenster abbrechen. Deshalb ein eigener Thread, der die Daten einliest und sie anzeigt. Dieser Thread muss, wie man sich denken kann permanent laufen.

Es muss allerdings auch möglich sein, Bedienungseingaben zuzulassen. Dies wollte ich über das Eventhandling im Hautprogramm machen. Ich dachte dann zur Synchronisation der beiden verwende ich Mutexes - was, wie man ja sieht nicht so ohne weiteres geht.

Wie könnte ich die Aufgabenstelung den verteilen, dass ich mein Ziel erreichen kann?

Danke,
Hugo
Benutzeravatar
remi_meier
Beiträge: 1078
Registriert: 29.08.2004 20:11
Wohnort: Schweiz

Beitrag von remi_meier »

Ich würde es jetzt so machen:
- Im Hauptprogramm die Event-Schleife (welche dann auch auf Events
reagiert)
- Einen Thread, der endlos läuft (dein Update-Thread). Dieser Thread
zeichnet nun die Daten auf ein Image. Wenn das Image fertig gezeichnet
wurde, musst du den Zugriff auf ein Imagegadget sperren (falls das
überhaupt irgendwo sonst gebraucht wird) und kannst das Image auf
das ImageGagdet() setzen (per SetGadgetState()). Du musst nur aufpassen,
dass der Mutex, welcher dein ImageGadget schützt, nicht gleichzeitig
vom Hauptprogramm verwendet wird! Ausser du nimmst TryLockMutex()
im Hauptprogramm, welches dann ja nicht blockiert, falls der Mutex
nicht verfügbar ist. Also einfach sicherstellen, dass (Wait)WindowEvent()
während dem Update per SetGadgetState() aufgerufen werden kann.
Der Thread kann nun z. B. mit Delay() warten, bis er erneut das Bild
im ImageGadget aktualisiert. Das Hauptprogramm läuft dann eigentlich
unabhängig vom Thread und kann auf Events reagieren.
Benutzeravatar
HeX0R
Beiträge: 3042
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3
Kontaktdaten:

Beitrag von HeX0R »

Hier empfiehlt sich per SendMessage_() bzw. PostMessage_() eigene Nachrichten an die Hauptschleife zu senden, damit diese sich dann um die Abarbeitung kümmern kann.

So verwende ich zumindest Threads und Fenster.
(Beispiel : http://www.purebasic.fr/german/viewtopi ... =10#121471)
Benutzeravatar
remi_meier
Beiträge: 1078
Registriert: 29.08.2004 20:11
Wohnort: Schweiz

Beitrag von remi_meier »

Ok, das ginge auch. Ist aber leider nicht plattformunabhängig. Aber etwas
ähnliches kann man sich ja auch selbst basteln, so mit ner LinkedList,
welche als Queue dient.

Obwohl, ich glaube es spricht nichts gegen ein SetGadgetState() in einem
Thread, oder?
Hugo
Beiträge: 56
Registriert: 30.03.2005 12:07
Wohnort: Nähe München

Beitrag von Hugo »

So ähnlich hatte ich mir das ja gedacht, bis ich dann auf diese Probleme gestoßen bin. Das Erfassen und Anzeigen der Daten im eigenen Thread ist ja nicht das Problem. Erst wenn der Anwender Eingaben machen kann, die über das Eventhandling im Hauptprogramm gesteuert werden, tritt das Problem ja auf. Denn die Eingaben müssen ausgewertet und dann auch angezeigt werden. Dieses Anzeigen sollte auch der Thread machen. Damit er das tut muss z.B. eine LL vom Hauptprogramm gefüllt werden und der Thread wertet sie aus und macht die Anzeige. Diese LL muss nun aber zwischen Hauptprogramm und Thread synchronisiert werden. Ich dachte dazu gibt es ja die Mutexes. Aber wie sich herausgestellt hat, kann ich die ja nicht im Hauptprogramm in dem das Eventhandling geschieht verwenden.

Wen ich SendMessage verwende, dann muss ich ja im Thread diese auch empfangen können - benötige da dann also eine Ereignisroutine. Diese ist doch aber schon im Hauptprogramm.

Wie kann ich denn nun dem Thread mitteilen, dass er eine bestimmte Aktion ausführt, wenn ich das nicht über Mutexes synchronisieren kann?

Vielen Dank,
Hugo
Benutzeravatar
remi_meier
Beiträge: 1078
Registriert: 29.08.2004 20:11
Wohnort: Schweiz

Beitrag von remi_meier »

Dann nimm halt deine LinkedList und arbeite im Hauptprogramm mit
TryLockMutex(). Du kannst ja dann so lange das Fenster disablen, bis
der Mutex frei wurde und dann kannst du die Message in die LL schreiben.

Code: Alles auswählen

message.l
repeat
  event = waitwindowevent(10)
  if event = Ich hab nen knopp gedrückt
    message = 20
    disablewindow()
  endif

  if message and trylockmutex()
    addelement(ll())
    ll() = message
    message = 0
    enablewindow()
    unlockmutex()
  endif
until quit
Du könntest auch eine LL anstelle der Variable 'message' verwenden,
um auch mehrere Messages speichern zu können.
Also nebst der globalen (geschützt durch mutex) noch ne lokale LL, die
die Messages zwischenspeichert.
Hugo
Beiträge: 56
Registriert: 30.03.2005 12:07
Wohnort: Nähe München

Beitrag von Hugo »

@remi_meier
Ich habe mein Programm erfolgreich auf die von dir empfohlene Art umgestellt und wollte dir für deine Hilfe danken!

Könnte es vielleicht sinnvoll sein, einen Vermerk dieser Problematik in die Dokumentation aufzunehmen. Denn ich denke dass anderen dieses Verhalten auch nicht auf anhieb einleuchtet.

Gruss,
Hugo
Benutzeravatar
remi_meier
Beiträge: 1078
Registriert: 29.08.2004 20:11
Wohnort: Schweiz

Beitrag von remi_meier »

Freut mich zu hören!

Jep, die Hilfe sollte schon noch etwas mehr zu diesem Thema bieten.
Rumprobieren ist zwar manchmal lustig, kann aber auch extrem lästig
sein, wenn man nicht so viel Zeit verlieren möchte :|
Antworten