[Win] Thread arbeitet nicht richtig bzw. hängt sich auch auf

Für allgemeine Fragen zur Programmierung mit PureBasic.
Beefi
Beiträge: 88
Registriert: 16.01.2017 17:38

[Win] Thread arbeitet nicht richtig bzw. hängt sich auch auf

Beitrag von Beefi »

Hallo zusammen,

ich habe ein seltsames Problem (bzw. zwei Probleme) mit Threads festgestellt, was mir bereits sehr viele Nerven gekostet hat, bis ich auf die Ursache kam.
Das Problem tritt bei mir unter Windows 10 x64 (andere OS nicht getestet) mit Version 5.71 und der neuen 5.73 auf (andere Versionen nicht getestet), sowie bei x32 und x64.
Es macht auch keinen Unterschied, ob man Thread-Sicher kompiliert oder nicht.

Problembeschreibung:
Einen Thread sollte man vernünftig über eine globale Variable beenden, damit der Thread selbstständig (z.B. per ProcedureReturn) ausläuft.
Jetzt hatte ich den Fall, eine Timer-Routine in einem Thread zu implementieren...die Zeit X, nach der diese Routine beendet werden soll, wird direkt aus einem SpinGadget herausgelesen.
Alleine die Tatsache, dass ich den Wert des SpinGadgets im Thread auslese, führt dazu, dass der Thread zwar einwandfrei läuft, sich jedoch nicht beenden kann (seht ihr z.B. am nicht umschaltenden OptionGadget). Ändere ich den Wert X
auf eine konstante Zahl, so beendet sich der Thread wie erwartet.

Dazu habe ich mal ein kleines Testprogramm erstellt, damit ihr den Bug reproduzieren könnt.

In der Event-Routine habe ich zwei mögliche Warte-Routinen eingetragen, die das Ende des Threads abwarten. Einmal per WaitThread (das Programm würde einfrieren, wenn man kein TimeOut setzt) und einmal eine selbstgeschriebene Routine, bei der das Programm ebenfalls hängen bleibt, bis nicht der TimeOut abgelaufen ist.
Durch Rätseln und Probieren habe ich bereits eine Lösung für das Problem gefunden: Setzt man ein einfaches WindowEvent() in die Timer-Routine, so wird der Thread ordnungsgemäß beendet (warum auch immer).

Zweites Problem:
Der Thread sollte alle 100ms ins Debug Fenster schreiben, bis der TimeOut (spinGadget-Text) abgelaufen ist. Danach gibts 1000 ms Pause, bis das ganze von Vorne beginnt.
Aus irgendeinem Grund, werden zwischen den 100ms jeweils zwei gleichzeitige Schreibvorgänge ins Debug-Fenster durchgeführt. Das darf laut Code gar nicht sein.


Hier das Test-Programm:

Code: Alles auswählen


EnableExplicit

Global EnableThread.l = 0
Global ThreadID.i

Global frmTestWindow.l = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 200, 150, "Thread-Bug Test")
Global optEnableThread.l = OptionGadget(#PB_Any, 40, 20, 100, 25, "Enable Thread")
Global optDisableThread.l = OptionGadget(#PB_Any, 40, 40, 100, 25, "Disable Thread")
Global spinTest.l = SpinGadget(#PB_Any, 40, 80, 100, 25, 0, 100)
SetGadgetText(spinTest, "5")




Procedure TestThread(*Value)
  
  Repeat
    
    Define Timer.q = ElapsedMilliseconds()
    Repeat
      If Not EnableThread
        ProcedureReturn
      EndIf
      
      Debug "Thread-Output"
      
      Delay(100)
      
    Until ElapsedMilliseconds() - Timer >= Val(GetGadgetText(spinTest)) * 1000 ; <- the reading of the spin-gadget creates the problem that the thread does not end! Why?
;     Until ElapsedMilliseconds() - Timer >= 5000 ; <- No problems with a constant number
    
    Debug "Pause"
    Delay(1000)
    
  ForEver
  
EndProcedure





Define Event.l

Repeat
  Event = WaitWindowEvent()
  
  If EventWindow() = frmTestWindow
    If Event = #PB_Event_CloseWindow
      CloseWindow(frmTestWindow)
      End
    EndIf
    
    If EventGadget() = optEnableThread
      EnableThread = 1
      ThreadID = CreateThread(@TestThread(), 0)
    EndIf
    
    If EventGadget() = optDisableThread
      EnableThread = 0
      
;       If IsThread(ThreadID)
;         WaitThread(ThreadID, 2000)  ; <- This freezes...resume only after time out!
;       EndIf
      
      Define Timer.q = ElapsedMilliseconds()
      Repeat
        If Not IsThread(ThreadID) : Break : EndIf
        Delay(100)
;         WaitWindowEvent() ; <- This command solve the freezing-problem!!! Why???
      Until ElapsedMilliseconds() - Timer >= 2000 ; <- This freezes also...resume only after timeout of 2000 ms. Or no problem with WaitWindowEvent()-Command
      
    EndIf
    
    
  EndIf
  

ForEver


Vielen Dank für die Unterstützung und viele Grüße,
Andi
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: [Win] Thread arbeitet nicht richtig bzw. hängt sich auch

Beitrag von NicTheQuick »

Zunächst mal ist das kein Bug, sondern ganz normal. Es ist allerhöchstens ein Fehler in der Dokumentation. :wink:

Dann hast du selbst einen Fehler eingebaut. Du überprüfst nirgendwo, ob Event = #PB_Event_Gadget, sondern fragst direkt EventGadget() ab. Das führt dazu, dass du gleich mehrere Threads erstellst, deswegen auch die doppelte Debug-Ausgabe. Ein sauberer Eventloop wäre das hier:

Code: Alles auswählen

Repeat
	Event = WaitWindowEvent()
	
	Select Event
		Case #PB_Event_CloseWindow
			CloseWindow(frmTestWindow)
			Break
		
		Case #PB_Event_Gadget
			Select EventGadget()
				Case optEnableThread
					; Thread nur starten, wenn es noch keinen gibt
					If ThreadID = 0
						ThreadID = CreateThread(@TestThread(), 0)
						EnableThread = 1
					EndIf
				
				Case optDisableThread
					EnableThread = 0
					
					While IsThread(ThreadID)
						Debug "Warte auf Beendigung des Threads..."
						WaitThread(ThreadID, 100)
						; Arbeite die Event-Queue ab, denn es häufen sich dauernd Events an, selbst man nur mit der Maus über das Fenster fährt
						; Nachteil: Eventuelle Klicks wie "Fenster schließen" gehen verloren.
						While WindowEvent(): Wend
					Wend
					ThreadID = 0
			EndSelect
	EndSelect
	
ForEver
Außerdem könnte es zusätzliche Probleme gemacht haben, dass zwei Threads liefen und die ThreadID des ersten verloren ging.

Insgesamt ist es keine gute Idee von Threads aus auf die im Hauptthread erstellten Fenster und Gadgets zuzugreifen. Unter Windows geht das manchmal, aber auch manchmal nicht. Auf andere Betriebssystem geht das gar nicht. Es sollte sich immer nur der Thread um Fensterangelegenheiten kümmern, der es auch erstellt hat. Am besten ist das der Hauptthread.

Für deinen Fall wäre es deswegen eine bessere Idee den Wert des SpinGadgets ebenfalls in eine globale Variable zu packen und diese Variable immer zu updaten, wenn du ein Change-Event vom SpinGadgets bekommen hast. So kannst du dir dann auch die Get-Zugriffe auf das Gadget sparen. In deiner aktuellen Lösung rufst du 10 Mal pro Sekunde GetGadgetText() auf, was man sich mit einer globalen Variablen sparen könnte.
Die Alternative zu globalen Variablen wäre eine Struktur mit verschiedenen Werten, die der Thread benötigt, und die man dann per Pointer als Thread-Argument übergibt.
Bild
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: [Win] Thread arbeitet nicht richtig bzw. hängt sich auch

Beitrag von mk-soft »

Um gadgets aus Threads zu ändern oder auf das beenden von Threads zu reagieren ist es am besten mit PostEvent zu arbeiten

Schau mal Mini Thread Control :wink:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Beefi
Beiträge: 88
Registriert: 16.01.2017 17:38

Re: [Win] Thread arbeitet nicht richtig bzw. hängt sich auch

Beitrag von Beefi »

Hi,

vielen Dank für die Antworten.

Dass ich direkt das EventGadget() abfrage, war ein Leichtsinnsfehler beim schnellen Erstellen eines Beispiel-Programms...im tatsächlichen Programm ist es nicht so. Aber das Problem scheint tatsächlich nur von der Funktion "Val(GetGadgetText(spinTest))" zu kommen. In der Doku steht ja glaub ich irgendwo, dass man das Ereignismanagement nicht über Threads abwickeln soll, aber dass alleine schon GetGadgetText() zu Problemen führt, hätte ich nicht gedacht. Es ist vielleicht nicht schön gelöst, dass ich in der Schleife diese Funktion nutze, aber immerhin liest sie ja nur den Text aus dem Gadget aus und sollte das Event-Management nicht durcheinander bringen (so dachte ich zumindest) :)
Die vorgeschlagene Variante über eine globale Variable ist besser...werde ich so umsetzen :allright:

@mk-soft:
Danke für den Tip mit PostEvent! Scheint sehr nützlich zu sein...über diese Funktion bin ich noch gar nicht gestolpert :allright:

Viele Grüße,
Andi
Antworten