Seite 1 von 1

Threads und Fenster: Best practice?

Verfasst: 17.10.2007 12:08
von Kiffi
Hallo,

bei meinem aktuellen Projekt habe ich einen arbeitsintensiven Prozess in
einen Thread ausgelagert. Innerhalb des Threads werden die Gadgets
eines zuvor geöffneten Progress-Fensters mit aktuellen Informationen
versorgt (Fortschrittsanzeige). Der Main-Eventloop sorgt dafür, dass die
Gadgets des Progress-Fensters aktualisiert werden.

Nun kann ich das Progress-Fenster ja erst schließen, wenn der Thread
beendet wurde. Nachfolgend ein Stück Pseudocode, anhand dessen man
erkennen kann, wie ich das in ungefähr gelöst habe,

Code: Alles auswählen

Repeat
  
  Select WaitWindowEvent(100)
    
    Case #PB_Event_Gadget
      
      Select EventGadget()
        Case #Work
          ProgressFensterOeffnen()
          WorkThread = StartWorkThread()
          
      EndSelect
      
    Case 0 ; TimeOut
      
      If WorkThread
        If IsThread(WorkThread)=0
          WorkThread = 0
          ProgressFensterSchliessen()
        EndIf
      EndIf
      
  EndSelect
  
Until Quit
Prinzipiell funktioniert obige Vorgehensweise problemlos. Dennoch würde
es mich interessieren, ob man das ganze nicht noch ein wenig besser
gestalten kann. Vielleicht gibt es ja auch eine Art 'best practice', nach der
man vorgehen kann.

Danke im voraus & Grüße ... Kiffi

Verfasst: 17.10.2007 14:52
von edel
Ich wuerde es so machen :

Code: Alles auswählen

ImportC "msvcrt.lib"
  _beginthread(proc,stack_size,param)  
EndImport

Structure event
  event.l
  hwnd.l
  job.l  
EndStructure

#WM_JOB_DONE  = #WM_USER 
#WM_JOB_ABORT = #WM_USER + 1

#JOB_NORMAL = 0
#JOB_ABORT  = 1

Procedure Thread(*ev.event)
  
  While 1
    ;THREAD WARTET BIS SETEVENT AUFGRUFEN WURDE
    WaitForSingleObject_(*ev\event,#INFINITE)  
                    
    ; JOB ANFANG    
    For i = 0 To 999
      
      If *ev\job = #JOB_ABORT: Break : EndIf  ; WAEHREND DES JOBS AUF ABBRUCH PRUEFEN
            
      SetGadgetState(1,i)
      Delay(2)   
      
    Next     
    ; JOB ENDE
    
    ; FENSTER MITTEILEN OB DER JOB ABGEBROCHEN ODER NORMAL BEENDET WURDE
    If i = 1000      
      PostMessage_(*ev\hwnd,#WM_JOB_DONE,0,0)
    Else
      PostMessage_(*ev\hwnd,#WM_JOB_ABORT,0,0)
    EndIf 
    
    ; ZURUECKSETZEN
    *ev\job = #JOB_NORMAL 
    
  Wend 
      
EndProcedure

Procedure Progress()
  
  hwnd = OpenWindow(1,#PB_Ignore,#PB_Ignore,200,100,"Progress Test",#PB_Window_WindowCentered,WindowID(0))
  
  CreateGadgetList(hwnd)
  ProgressBarGadget(1,10,10,180,20,0,1000)
  ButtonGadget(2,60,50,75,23,"Stop")
  
  DisableWindow(0,1)  

EndProcedure

Procedure Main()
  Protected hwnd
  Protected ev.event
  Protected thread
  
  hwnd = OpenWindow(0,#PB_Ignore,#PB_Ignore,430,150,"Thread Test")
  
  CreateGadgetList(hwnd)
  ButtonGadget(0,20,20,75,23,"start")  
  TextGadget(3,20,50,75,23,"Status : ")
  
  ; EVENT ERZEUGEN
  ev\event = CreateEvent_(0,0,0,0)
  ; MESSAGE FENSTER SETZEN
  ev\hwnd  = hwnd
  
  ; THREAD STARTEN
  _beginthread(@Thread(),0,@ev)
      
  Repeat  
  
    Select WaitWindowEvent()    
      Case #PB_Event_Gadget
        
        Select EventGadget() 
          Case 0
            Progress()
            SetGadgetText(3,"Status : run")
            ; THREAD MITTEILEN DAS ER NUN STARTEN KANN
            SetEvent_(ev\event)               
          Case 2
            ; THREAD MITTEILEN DAS ER STOPPEN KANN
            ev\job = #JOB_ABORT
        EndSelect
          
    Case #WM_JOB_DONE
    
      SetGadgetText(3,"Status : done")
      DisableWindow(0,0)
      CloseWindow(1)
      
    Case #WM_JOB_ABORT
    
      SetGadgetText(3,"Status : abort")
      DisableWindow(0,0)
      CloseWindow(1)  
      
    Case #PB_Event_CloseWindow 
          
      CloseHandle_(ev\event)    
      Break      
      
    EndSelect
     
  ForEver
    
EndProcedure:Main()
Sieht etwas komplex aus, ist aber eigentlich sehr einfach.
Der Thread wird gleich zu beginn gestartet und mit
WaitForSingleObject "geparkt". Sobald man nun mit SetEvent,
das vorher erzeugte, Event feuert, startet die eigentliche Arbeit
in dem Thread. WaitForSingleObject ist in etwa mit WaitWindowEvent
zu vergleichen. Sie machen so lange nichts bis etwas ankommt.
Ausserdem kann man so den Thread von aussen leicht steuern und
es wird keine extra CPU Zeit benoetigt.

Das Idee stammt aus dem Buch "Windows Programmierung" von Petzold.

Verfasst: 18.10.2007 12:21
von jpd
Hi Edel,

finde deinen code sehr interessant..

habe dazu eine frage welcher unterschied existiert zwischen

waitforsingleobject und waitthread?`

und wieso verwendest'du _beginthread anstatt createthread?

danke
jpd

Verfasst: 18.10.2007 12:46
von edel
Das sind doch auch nur Api-Wrapper. Die machen im Prinzip das
gleiche, nur das ich bei den Api Funktion nachlesen kann, wie,
was und wo etwas passiert.


Aber hier mal mit PB

Code: Alles auswählen

Structure event
  hwnd.l
  job.l 
  th.l
EndStructure

#WM_JOB_DONE  = #WM_USER
#WM_JOB_ABORT = #WM_USER + 1

#JOB_NORMAL = 0
#JOB_ABORT  = 1

Procedure Thread(*ev.event)
 
  While 1
 
    PauseThread(*ev\th)               
    
    For i = 0 To 999     
      If *ev\job = #JOB_ABORT: Break : EndIf            
      SetGadgetState(1,i)
      Delay(2)        
    Next     

    If i = 1000     
      PostMessage_(*ev\hwnd,#WM_JOB_DONE,0,0)
    Else
      PostMessage_(*ev\hwnd,#WM_JOB_ABORT,0,0)
    EndIf

  Wend
     
EndProcedure

Procedure Progress()
 
  hwnd = OpenWindow(1,#PB_Ignore,#PB_Ignore,200,100,"Progress Test",#PB_Window_WindowCentered,WindowID(0))
 
  CreateGadgetList(hwnd)
  ProgressBarGadget(1,10,10,180,20,0,1000)
  ButtonGadget(2,60,50,75,23,"Stop")
 
  DisableWindow(0,1) 

EndProcedure

Procedure Main()
  Protected hwnd
  Protected ev.event
  Protected thread
 
  hwnd = OpenWindow(0,#PB_Ignore,#PB_Ignore,430,150,"Thread Test")
 
  CreateGadgetList(hwnd)
  ButtonGadget(0,20,20,75,23,"start") 
  TextGadget(3,20,50,75,23,"Status : ")
 
  ev\hwnd = hwnd
  ev\th   = CreateThread(@Thread(),@ev)
     
  Repeat  
    Select WaitWindowEvent()   
      Case #PB_Event_Gadget       
        Select EventGadget()
          Case 0
            Progress()
            ev\job = #JOB_NORMAL
            SetGadgetText(3,"Status : run")            
            ResumeThread(ev\th)
          Case 2
            ev\job = #JOB_ABORT
        EndSelect         
    Case #WM_JOB_DONE   
      SetGadgetText(3,"Status : done")
      DisableWindow(0,0)
      CloseWindow(1)     
    Case #WM_JOB_ABORT   
      SetGadgetText(3,"Status : abort")
      DisableWindow(0,0)
      CloseWindow(1)      
    Case #PB_Event_CloseWindow
      Break          
    EndSelect
     
  ForEver
   
EndProcedure:Main()


Edit:
Lese gerade das WaitThread unter PB dafuer da ist zu warten bis der
Thread beendet worden ist, sowas ist in dem Code ja nicht wirklich
nuetzlich.

Verfasst: 18.10.2007 14:02
von jpd
Hi Edel,

Danke!

also waitforsinglethread = pausethread


ich glaube ich werde meinen project ein bisshien umschreiben :)

Ciao
jpd

Verfasst: 18.10.2007 15:14
von edel
jpd hat geschrieben: also waitforsinglethread = pausethread
Nein, PauseThread ist SuspendThread. Was hier aber zu dem gleichen
Ergebnis fuehrt.

Verfasst: 18.10.2007 16:37
von jpd
Hi Edel,

so wie ich verstehe sind waitforsingleobject und pausethread gleich wenn als parameter "maximum time to wait" #infinite angegeben wird!

Ciao
jpd

Verfasst: 23.10.2007 14:01
von Kiffi
@edel:

Vielen Dank für Deinen Tipp! :allright:
Diese Vorgehensweise kannte ich noch nicht.

Allerdings werde ich vorerst nur die PostMessage()-Lösung verwenden, weil
ich beim Starten eines Threads, der dann mittels PauseThread() auf seinen
Einsatz wartet, ein undefinierbares "ich-weiß-nicht-so-recht"-Gefühl habe.

Getreu dem Motto: "Watt der Bauer nicht kennt..." ;-)

Grüße ... Kiffi