Thread synchronization with gui event loop?

Just starting out? Need help? Post your questions and find answers here.
User avatar
skywalk
Addict
Addict
Posts: 3972
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Thread synchronization with gui event loop?

Post by skywalk »

I'm modifying a larger code base from timers to semaphores and it works. But, the visual feedback skips actual thread completions. How can I modify the code to have the editor gadget list each thread completion, (without reading the thread's physical disk writes)?
Skipping examples:
Click on any menu item and hold before selecting and the thread cranks along as it should, but the gui event loop loses track of the thread messages.
Hit the [Escape] key at various points. You will see the 1st resume occurs at 2 skips and later resumes only 1 skip?
Thanks to mk-soft for his example code.

Code: Select all

; http://www.purebasic.fr/english/viewtopic.php?f=12&t=64084&hilit=simple+thread+control
; Comment:  Simple Thread Control 
; Author:   mk-soft
; Version:  v1.03
; Create:   22.11.2015
; Update:   01.09.2016
; Update:   20170816, mangled by skywalk
;==================================================================================
; COMPILER OPTIONS:
;   [ ] Use Compiler:   PureBasic 5.6 (x64)
;   [ ] Use Icon:       
;   [ ] Enable inline ASM support
;   [x] Create threadsafe executable
;   [ ] Enable OnError lines support
;   [x] Enable XP skin support
;   [ ] Request Administrator mode for Windows Vista
;   [ ] Request User mode for Windows Vista (no virtualization)
;   Library Subsystem:
;   Executable Format:  Windows  ;|Console|Shared DLL
;   CPU:                All      ;|Dynamic|w/MMX|w/3DNOW|w/SSE|w/SSE2
;   File Format:        UTF-8
;==================================================================================
EnableExplicit
Enumeration #PB_Event_FirstCustomValue
  #thr_event_state
EndEnumeration
Enumeration
  #thr_cmd_nothing         = 0
  #thr_cmd_start
  #thr_cmd_pause
  #thr_cmd_continue
  #thr_cmd_stop
  #thr_now_nothing       = 0
  #thr_now_running
  #thr_now_paused
  #thr_now_continued
  #thr_now_stopped
  #thr_now_finished
  ;-Thread data
  #thr_dat_none            = 0
  #thr_dat_new                 ; signal new data available.
EndEnumeration
Structure thread_INFO
  n.i
  h.i
  sig.i
  cmd.i
  now.i
  ; data
  dat.i
  nPts.i
  gadStb.i
EndStructure
Global thr1.thread_INFO
;//////////////////////////////////////////////////////////////////
; GUI elements
Enumeration
  #gadStart     = 0
  #gadPause
  #gadStop
  #gadEdr
  #gadStb       = 0
EndEnumeration
; Global data structure
Structure myData_INFO
  nPts.l
  name$
EndStructure
Global myD.myData_INFO
;-{ PROCEDURES
Procedure thr_Do(*me.thread_INFO)
  Protected.i i
  Protected.d x
  Debug "Thread started at nPts = " + Str(myD\nPts)
  *me\sig = CreateSemaphore(1)
  Repeat
    Select *me\cmd
    Case #thr_cmd_start
      *me\cmd = #thr_cmd_nothing
      If *me\now <= #thr_now_nothing Or *me\now >= #thr_now_finished
        *me\now = #thr_now_running
        *me\dat = #thr_dat_none
        PostEvent(#thr_event_state, 0, *me, *me\now)
        ;Delay(30)
      EndIf
    Case #thr_cmd_pause
      *me\cmd = #thr_cmd_nothing
      If *me\now = #thr_now_running
        Debug "Thread paused at nPts = " + Str(myD\nPts)
        *me\now = #thr_now_paused
        PostEvent(#thr_event_state, 0, *me, *me\now)
        ;Delay(30)
      EndIf
    Case #thr_cmd_continue
      *me\cmd = #thr_cmd_nothing
      If *me\now = #thr_now_paused
        Debug "Thread resumed at nPts = " + Str(myD\nPts)
        *me\now = #thr_now_running
        PostEvent(#thr_event_state, 0, *me, *me\now)
        ;Delay(30)
      EndIf
    Case #thr_cmd_stop
      *me\cmd = #thr_cmd_nothing
      If *me\now < #thr_now_stopped
        Debug "Thread stopped at nPts = " + Str(myD\nPts)
        *me\now = #thr_now_stopped
        PostEvent(#thr_event_state, 0, *me, *me\now)
        ;Delay(30)
      EndIf
    EndSelect
    If *me\now = #thr_now_paused Or *me\now = #thr_now_nothing
      WaitSemaphore(*me\sig)
    ElseIf *me\now = #thr_now_stopped
      Break
    EndIf
    ;////////////////////////////////////////////////////////////
    ; Simulate some working thread code...
    For i = 0 To 1e6
      x = Cos(i) * Sin(i)
    Next i
    myD\nPts + 1
    OpenFile(99, #PB_Compiler_FilePath + "z.txt", #PB_File_Append)
    WriteStringN(99, "Finished -> " + RSet(Str(myD\nPts), 10), #PB_Ascii)
    CloseFile(99)
    *me\dat = #thr_dat_new
    PostEvent(#thr_event_state, 0, *me, *me\now)
    ;////////////////////////////////////////////////////////////
  Until *me\now = #thr_now_stopped
  Debug "Thread released at nPts = " + Str(myD\nPts)
  *me\now = #thr_now_finished
  PostEvent(#thr_event_state, 0, *me, *me\now)
  ; Release thread
  FreeSemaphore(*me\sig)
  *me\sig = 0
EndProcedure
;-} PROCEDURES
;-{ TEST
CompilerIf 1
  Procedure Main()
    Protected.i evWW, evM
    Protected.s r$
    If OpenWindow(0, #PB_Any, #PB_Any, 600, 400, "Thread Control+", #PB_Window_ScreenCentered | #PB_Window_SystemMenu | #PB_Window_SizeGadget)
      CreateStatusBar(#gadStb, WindowID(0))
      AddStatusBarField(1000)
      ButtonGadget(#gadStart, 10, 10, 120, 25, "Start")
      ButtonGadget(#gadPause, 140, 10, 120, 25, "Pause/Resume")
      ButtonGadget(#gadStop, 400, 10, 120, 25, "Stop")
      EditorGadget(#gadEdr, 10, 40, WindowWidth(0)-20, WindowHeight(0)-MenuHeight()-StatusBarHeight(#gadStb)-25-20)
      If CreateMenu(0, WindowID(0))
        MenuTitle("File")
        MenuItem(1, "&Load...")
        MenuItem(2, "Save")
        MenuItem(3, "Save As...")
        MenuBar()
        MenuItem(5, "&Quit")
        MenuTitle("Thread")
        MenuItem(7, "Start")
        MenuItem(8, "Pause/Resume")
        MenuItem(9, "Stop")
      EndIf
      AddKeyboardShortcut(0, #PB_Shortcut_Escape, 8)
      AddKeyboardShortcut(0, #PB_Shortcut_Control | #PB_Shortcut_Q, 5)
      thr1\gadStb = #gadStb
      Repeat
        Select WaitWindowEvent()
        Case #PB_Event_Menu
          evM = EventMenu()
          Select evM
          Case 5
            Break
          Case 7
            If Not IsThread(thr1\n)
              thr1\n = CreateThread(@thr_Do(), @thr1)
            EndIf
            thr1\cmd = #thr_cmd_start
            If thr1\sig
              SignalSemaphore(thr1\sig)
            EndIf
          Case 8    ; Pause/Resume
            If thr1\now = #thr_now_running
              thr1\cmd = #thr_cmd_pause
              Debug "Paused"
            ElseIf thr1\now = #thr_now_paused
              thr1\cmd = #thr_cmd_continue
              If thr1\sig
                SignalSemaphore(thr1\sig)
              EndIf
              Debug "Resumed"
            EndIf
          Case 9   ; Stop
            Debug "Stop"
            thr1\cmd = #thr_cmd_stop
            If thr1\sig
              SignalSemaphore(thr1\sig)
            EndIf
          EndSelect
        Case #thr_event_state
          Select thr1\now
          Case #thr_now_nothing
            StatusBarText(thr1\gadStb, 0, "Nothing")
          Case #thr_now_running
            If thr1\dat = #thr_dat_new
              thr1\dat = #thr_dat_none
              r$ = "Finished -> " + RSet(Str(myD\nPts), 10)
              AddGadgetItem(#gadEdr, -1, r$)
              SendMessage_(GadgetID(#gadEdr), #EM_SETSEL, -1, -1)  ; Scroll to bottom.
              SetGadgetText(#gadPause, "Pause")
              StatusBarText(thr1\gadStb, 0, "Running")
            EndIf
          Case #thr_now_paused
            SetGadgetText(#gadPause, "Resume")
            StatusBarText(thr1\gadStb, 0, "Paused")
          Case #thr_now_continued
            SetGadgetText(#gadPause, "Pause")
            StatusBarText(thr1\gadStb, 0, "Resumed")
          Case #thr_now_stopped
            StatusBarText(thr1\gadStb, 0, "Stopped")
          Case #thr_now_finished
            StatusBarText(thr1\gadStb, 0, "Finished")
            ;WaitThread(thr1\n, 3000)
            ;KillThread(thr1\n)
            thr1\n = 0
          EndSelect
        Case #PB_Event_Gadget
          Select EventGadget()
          Case #gadStart
            If Not IsThread(thr1\n)
              thr1\n = CreateThread(@thr_Do(), @thr1)
            EndIf
            thr1\cmd = #thr_cmd_start
            If thr1\sig
              SignalSemaphore(thr1\sig)
            EndIf
          Case #gadPause
            If thr1\now = #thr_now_running
              thr1\cmd = #thr_cmd_pause
            ElseIf thr1\now = #thr_now_paused
              thr1\cmd = #thr_cmd_continue
              If thr1\sig
                SignalSemaphore(thr1\sig)
              EndIf
            EndIf
          Case #gadStop
            thr1\cmd = #thr_cmd_stop
            If thr1\sig
              SignalSemaphore(thr1\sig)
            EndIf
          EndSelect
        Case #PB_Event_CloseWindow
          Break
        EndSelect
      ForEver
    EndIf
  EndProcedure
  Main()
CompilerEndIf
;-} TEST
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Thread synchronization with gui event loop?

Post by mk-soft »

The way over PostEvent is right. For updates gadget i have a module ThreadToGUI.
Link: http://www.purebasic.fr/english/viewtop ... 12&t=66180

For wait in the thread of user input show SendEvent Example

I hope this help you

P.S. For wait thread to done all Events use Command DoWait()
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
skywalk
Addict
Addict
Posts: 3972
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Thread synchronization with gui event loop?

Post by skywalk »

Thanks, I saw your ThreadToGUI lib but decided to make smaller inline changes to my code.
I tried making the Thread wait for a return signal from the main event loop?
Is it as simple as this?...
Main EventLoop sends -> SignalSemaphore(thr1\sig)
Thread EventLoop pauses with -> WaitSemaphore(thr1\sig)
Both Main EventLoop and Thread EventLoop see the Global data structure.
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Thread synchronization with gui event loop?

Post by mk-soft »

Yes, is simple over Semahore.

Thread send a request to Main and go to WaitSemaphore. Main answer with result and SignalSemaphore...

Code: Select all

;-TOP

; ***************************************************************************************

;-Begin Of SendEvent

; Comment : SendEvent
; Author  : mk-soft
; Version : v1.06
; Create  : unknown
; Update  : 07.08.2016

;- Structure
Structure udtSendEvent
  Signal.i
  Result.i
  *pData
EndStructure

; ---------------------------------------------------------------------------------------

Procedure SendEvent(Event, Window = 0, Object = 0, EventType = 0, pData = 0, Semaphore = 0)
  
  Protected MyEvent.udtSendEvent, result
  
  With MyEvent
    If Semaphore
      \Signal = Semaphore
    Else
      \Signal = CreateSemaphore()
    EndIf
    \pData = pData
    PostEvent(Event, Window, Object, EventType, @MyEvent)
    WaitSemaphore(\Signal)
    result = \Result
    If Semaphore = 0
      FreeSemaphore(\Signal)
    EndIf
  EndWith
  
  ProcedureReturn result
  
EndProcedure

; ---------------------------------------------------------------------------------------

Procedure SendEventData(*MyEvent.udtSendEvent)
  ProcedureReturn *MyEvent\pData
EndProcedure

; ---------------------------------------------------------------------------------------

Procedure DispatchEvent(*MyEvent.udtSendEvent, result)
  *MyEvent\Result = result
  SignalSemaphore(*MyEvent\Signal)
EndProcedure

;- End Of SendEvent

; ***************************************************************************************

;- Example

CompilerIf #PB_Compiler_IsMainFile
  
  EnableExplicit
  
  ; ***************************************************************************************
  
  ;- Memory string helper
  
  Procedure AllocateString(String.s)
    Protected *mem.string
    *mem = AllocateStructure(string)
    If *mem
      *mem\s = String
    EndIf
    ProcedureReturn *mem
  EndProcedure
  
  ; ---------------------------------------------------------------------------------------
  
  Procedure.s FreeString(*mem.string)
    Protected result.s
    If *mem
      result = *mem\s
      FreeStructure(*mem)
    EndIf
    ProcedureReturn result
  EndProcedure
  
  ; ***************************************************************************************

  Enumeration
    #Window
  EndEnumeration
  
  ;- Constants
  Enumeration #PB_Event_FirstCustomValue
    #My_Event_Messagebox
    #My_Event_Inputbox
  EndEnumeration
  
  Procedure Thread1(Null)
    
    Debug "Init Thread 1"
    Protected result
    
    Repeat
      Delay(500)
      result = SendEvent(#My_Event_Messagebox, 0, 0, 0, Random(100))
      Select result
        Case #PB_MessageRequester_Yes
          Debug "#PB_MessageRequester_Yes"
        Case #PB_MessageRequester_No
          Debug "#PB_MessageRequester_No"
        Case #PB_MessageRequester_Cancel
          Debug "#PB_MessageRequester_Cancel"
      EndSelect
    Until result = #PB_MessageRequester_Cancel
    
    Debug "Exit Thread 1"
    
  EndProcedure
  
  Procedure Thread2(Null)
    
    Debug "Init Thread 2"
    Protected result, text.s
    
    Repeat
      Delay(500)
      result = SendEvent(#My_Event_Inputbox, 0, 0, 0, Random(100))
      text = FreeString(result)
      Debug text
    Until text = ""
    
    Debug "Exit Thread 2"
    
  EndProcedure
  
  Procedure Main()
    Protected exit, th1, th2
    Protected MyEvent, value, result, text.s 
    
    If OpenWindow(#Window, 0, 0, 800, 600, "WindowTitle", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)
      th1 = CreateThread(@Thread1(), #Null)
      Repeat
        Select WaitWindowEvent()
          Case #PB_Event_CloseWindow
            Break
            
          Case #PB_Event_Gadget
            
          Case #My_Event_Messagebox
            MyEvent = EventData()
            value = SendEventData(MyEvent)
            result = MessageRequester("Thread 1", "What happens next?" + #LF$ + "(Code " + value + ")", #PB_MessageRequester_YesNoCancel)
            DispatchEvent(MyEvent, result)
            If result = #PB_MessageRequester_Cancel
              th2 = CreateThread(@Thread2(), #Null)
            EndIf
            
          Case #My_Event_Inputbox
            MyEvent = EventData()
            text = InputRequester("Thread 2", "Name:", "")
            result = AllocateString(text)
            DispatchEvent(MyEvent, result)
            
        EndSelect
      ForEver
      
      If IsThread(th1)
        KillThread(th1)
      EndIf
      If IsThread(th2)
        KillThread(th2)
      EndIf
      
    EndIf
  EndProcedure : Main()
  
CompilerEndIf
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
skywalk
Addict
Addict
Posts: 3972
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Thread synchronization with gui event loop?

Post by skywalk »

Ok, I'm trying to make that happen without requesters blocking the event loop.
If you run my example code, do you see the skips?
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Thread synchronization with gui event loop?

Post by mk-soft »

With SendEvent you can return a result.
Does not have to be immediate. But the data MyEvent for later processing does not overwrite.

Property times a small example that it with semaphore functioned perfectly.

Edit

MenuItems or Window events such as MoveWindow interrupt the EventLoop.
That is normal.
For Windows, send the entry Direct from the thread to the gadget (SendMessage ...). ONLY WITH WINDOW!

Otherwise, use PostEvent to send the entry with data. These are added after the EventLoop is free again

;... Edit. Please wait... Ready!

Code: Select all

;-TOP

; ***************************************************************************************

Procedure AllocateString(String.s)
  Protected *mem.string
  *mem = AllocateStructure(string)
  If *mem
    *mem\s = String
  EndIf
  ProcedureReturn *mem
EndProcedure

; ---------------------------------------------------------------------------------------

Procedure.s FreeString(*mem.string)
  Protected result.s
  If *mem
    result = *mem\s
    FreeStructure(*mem)
  EndIf
  ProcedureReturn result
EndProcedure

; ***************************************************************************************

Structure udtWork
  ThreadID.i
  Signal.i
  Exit.i
  Count.i
EndStructure

Enumeration #PB_Event_FirstCustomValue
  #My_Event_Data  
EndEnumeration

Procedure AddDataToList()
  Protected *pStr, text.s, count
  *pStr = EventData()
  text = FreeString(*pStr)
  AddGadgetItem(2, -1, text)
  count = CountGadgetItems(2)
  SetGadgetState(2, count - 1)
  SetGadgetState(2, -1)
  If count > 200
    RemoveGadgetItem(2, 0)
  EndIf
EndProcedure

Procedure thWork(*data.udtWork)
  Protected *pStr
  Debug "Init Thread"
  With *data
    WaitSemaphore(\Signal)
    Repeat
      If TrySemaphore(\Signal)
        If \Exit
          Break
        Else
          WaitSemaphore(\Signal)
        EndIf
      EndIf
      *pstr = AllocateString("Counter: " + Str(\Count))
      PostEvent(#My_Event_Data, 0, 0, 0, *pstr)
      Delay(500)
      \Count + 1
    Until \Exit
  EndWith
  Debug "Exit Thread"
EndProcedure

Global thData.udtWork


Procedure Main()
  If OpenWindow(0, #PB_Ignore, #PB_Ignore, 600, 480, "Thread", #PB_Window_SystemMenu)
    ButtonGadget(0, 10,450,120,25, "Start/Stop")
    ButtonGadget(1, 130,450,120,25, "Exit")
    ListViewGadget(2, 5, 5, 590, 440)
    
    BindEvent(#My_Event_Data, @AddDataToList())
    
    thData\Signal = CreateSemaphore()
    thData\ThreadID = CreateThread(@thWork(), thData)

    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_CloseWindow
          thData\Exit = 1
          SignalSemaphore(thData\Signal)
          Delay(1000)
          Break
        Case #PB_Event_Gadget
          Select EventGadget()
            Case 0
              SignalSemaphore(thData\Signal)
            Case 1
              thData\Exit = 1
              SignalSemaphore(thData\Signal)
          EndSelect
      EndSelect
      
    ForEver
  EndIf
  
EndProcedure : Main()
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
skywalk
Addict
Addict
Posts: 3972
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Thread synchronization with gui event loop?

Post by skywalk »

Thanks, your hint to make the thread start in WaitSemaphore() mode helped. :D
Here is the modified code with no skipping or duplicates in the gui editor gadget.
I had to use an array buffer instead of a simple running string buffer. Now any user actions that interrupt the gui event loop(hold and drag a scrollbar, click and hold a menu item) will be updated upon the user's release.

Code: Select all

; http://www.purebasic.fr/english/viewtopic.php?f=12&t=64084&hilit=simple+thread+control
; Comment:  Simple Thread Control 
; Author:   mk-soft
; Version:  v1.03
; Create:   22.11.2015
; Update:   01.09.2016
; Update:   20170816, mangled by skywalk
;==================================================================================
; COMPILER OPTIONS:
;   [ ] Use Compiler:   PureBasic 5.6 (x64)
;   [ ] Use Icon:       
;   [ ] Enable inline ASM support
;   [x] Create threadsafe executable
;   [ ] Enable OnError lines support
;   [x] Enable XP skin support
;   [ ] Request Administrator mode for Windows Vista
;   [ ] Request User mode for Windows Vista (no virtualization)
;   Library Subsystem:
;   Executable Format:  Windows  ;|Console|Shared DLL
;   CPU:                All      ;|Dynamic|w/MMX|w/3DNOW|w/SSE|w/SSE2
;   File Format:        UTF-8
;==================================================================================
EnableExplicit
CompilerIf #PB_Compiler_Thread = 0
  CompilerError "Requires Compiler Option -> [x] Create threadsafe executable."
CompilerEndIf
Enumeration #PB_Event_FirstCustomValue
  #thr_event_now
EndEnumeration
Enumeration
  #thr_cmd_none          = 0
  #thr_cmd_run
  #thr_cmd_pause
  #thr_cmd_resume
  #thr_cmd_stop
  #thr_now_none          = 0
  #thr_now_running
  #thr_now_paused
  #thr_now_stopped
  #thr_now_done
EndEnumeration
Structure thread_INFO
  n.i
  h.i
  sig.i
  cmd.i
  now.i
  ; data
  nthPt.i
  gadStb.i
EndStructure
Global thr1.thread_INFO
;//////////////////////////////////////////////////////////////////
; GUI elements
Enumeration
  #gadRun               = 0
  #gadPause
  #gadStop
  #gadEdr
  #gadStb               = 0
  #mnuQuit              = 5
  #mnuThrRun            = 7
  #mnuThrPause_Resume   = 8
  #mnuThrStop           = 9
EndEnumeration
; Global data structure
Structure myData_INFO
  nthPt.l
  nthPtgui.l
  Array dat$(10000)     ; Pre-fill or ReDim later based on specific data
EndStructure
Global myD.myData_INFO
;-{ PROCEDURES
Procedure thr_Do(*me.thread_INFO)
  Protected.i i
  Protected.d x
  Debug "Thread started at nthPt = " + Str(myD\nthPt)
  *me\sig = CreateSemaphore(1)
  Repeat
    Select *me\cmd
    Case #thr_cmd_run
      *me\cmd = #thr_cmd_none
      If *me\now = #thr_now_none Or *me\now = #thr_now_done
        *me\now = #thr_now_running
      EndIf
    Case #thr_cmd_pause
      *me\cmd = #thr_cmd_none
      If *me\now = #thr_now_running
        Debug "Thread paused at nthPt = " + Str(myD\nthPt)
        *me\now = #thr_now_paused
      EndIf
    Case #thr_cmd_resume
      *me\cmd = #thr_cmd_none
      If *me\now = #thr_now_paused
        Debug "Thread resumed at nthPt = " + Str(myD\nthPt)
        *me\now = #thr_now_running
      EndIf
    Case #thr_cmd_stop
      *me\cmd = #thr_cmd_none
      If *me\now < #thr_now_stopped
        Debug "Thread stopped at nthPt = " + Str(myD\nthPt-1)
        *me\now = #thr_now_stopped
      EndIf
    EndSelect
    If *me\now = #thr_now_running
      ;////////////////////////////////////////////////////////////
      ; Simulated working thread code...
      For i = 0 To 1e6
        x = Cos(i) * Sin(i)
      Next i
      If myD\nthPt
        OpenFile(99, #PB_Compiler_FilePath + "z.txt", #PB_File_Append)
      Else
        CreateFile(99, #PB_Compiler_FilePath + "z.txt")
      EndIf
      myD\dat$(myD\nthPt) = "nthPt -> " + RSet(Str(myD\nthPt), 10)
      WriteStringN(99, myd\dat$(myD\nthPt), #PB_Ascii)
      CloseFile(99)
      myD\nthPt + 1
      ;////////////////////////////////////////////////////////////
    EndIf
    PostEvent(#thr_event_now, 0, *me, *me\now)
    If TrySemaphore(*me\sig)
      If *me\cmd = #thr_cmd_stop
        *me\now = #thr_now_stopped
      ElseIf *me\now = #thr_now_paused
        WaitSemaphore(*me\sig)
      EndIf
    EndIf
    If *me\now = #thr_now_paused Or *me\now = #thr_now_none
      WaitSemaphore(*me\sig)
    EndIf
  Until *me\now = #thr_now_stopped
  Debug "Thread released at nthPt = " + Str(myD\nthPt-1)
  *me\now = #thr_now_done
  PostEvent(#thr_event_now, 0, *me, *me\now)
  ; Release thread
  FreeSemaphore(*me\sig)
  *me\sig = 0
EndProcedure
;-} PROCEDURES
;-{ TEST
CompilerIf 1
  Global.i gadEdr_CB
  Procedure gui_thr_update(from$)
    If thr1\n And (thr1\now <> #thr_now_stopped)
      Protected.s r$
      If myd\nthPtgui < myD\nthPt
        r$ = myD\dat$(myD\nthPtgui) + ": from -> " + from$
        AddGadgetItem(#gadEdr, -1, r$)
        SendMessage_(GadgetID(#gadEdr), #EM_SETSEL, -1, -1)  ; Scroll to bottom.
        myd\nthPtgui + 1
      EndIf
    EndIf
  EndProcedure
  Macro gui_thr_run()
    If Not IsThread(thr1\n)
      thr1\n = CreateThread(@thr_Do(), @thr1)
    EndIf
    If thr1\now = #thr_now_none Or thr1\now = #thr_now_done
      thr1\cmd = #thr_cmd_run
      If thr1\sig
        SignalSemaphore(thr1\sig)
      EndIf
      SetGadgetText(#gadPause, "Pause")
    EndIf
  EndMacro
  Macro gui_thr_pause()
    If thr1\now = #thr_now_running
      thr1\cmd = #thr_cmd_pause
      Debug "Paused..."
      SetGadgetText(#gadPause, "Resume")
    ElseIf thr1\now = #thr_now_paused
      thr1\cmd = #thr_cmd_resume
      If thr1\sig
        SignalSemaphore(thr1\sig)
      EndIf
      Debug "Resumed..."
      SetGadgetText(#gadPause, "Pause")
    EndIf
  EndMacro
  Macro gui_thr_stop()
    thr1\cmd = #thr_cmd_stop
    Debug "Stopped..."
    If thr1\sig
      SignalSemaphore(thr1\sig)
    EndIf
    SetGadgetText(#gadPause, "Pause")
  EndMacro
  CompilerIf 0  ; Updating gui during scrollbar events not required since using array buffer.
    Procedure.i edr_CB(hW.i, Msg.i, wP.i, lP.i)
      Protected.i ri = CallWindowProc_(gadEdr_CB, hw, Msg, wP, lP)
      If msg = #WM_VSCROLL
        If myd\nthPtgui < myD\nthPtgui
          gui_thr_update("CB")
        Else
          Debug "scroll -> " + Str(myD\nthPtgui)
        EndIf
      EndIf
      ProcedureReturn ri
    EndProcedure
  CompilerEndIf
  Procedure Main()
    Protected.i evWW, evM
    Protected.s r$
    If OpenWindow(0, #PB_Any, #PB_Any, 600, 400, "Thread Control+GUI Sync", #PB_Window_ScreenCentered | #PB_Window_SystemMenu | #PB_Window_SizeGadget)
      CreateStatusBar(#gadStb, WindowID(0))
      AddStatusBarField(1000)
      ButtonGadget(#gadRun, 10, 10, 120, 25, "Run")
      ButtonGadget(#gadPause, 140, 10, 120, 25, "Pause/Resume")
      ButtonGadget(#gadStop, 400, 10, 120, 25, "Stop")
      EditorGadget(#gadEdr, 10, 40, WindowWidth(0)-20, WindowHeight(0)-MenuHeight()-StatusBarHeight(#gadStb)-25-20)
      CompilerIf 0
        ; EditorGadget callback for unsupported events like scrollbar
        gadEdr_CB = SetWindowLongPtr_(GadgetID(#gadEdr), #GWLP_WNDPROC, @edr_CB()) 
      CompilerEndIf
      If CreateMenu(0, WindowID(0))
        MenuTitle("File")
        MenuItem(1, "&Load...")
        MenuItem(2, "Save")
        MenuItem(3, "Save As...")
        MenuBar()
        MenuItem(#mnuQuit, "&Quit")
        MenuTitle("Thread")
        MenuItem(#mnuThrRun, "Run")
        MenuItem(#mnuThrPause_Resume, "Pause/Resume")
        MenuItem(#mnuThrStop, "Stop")
      EndIf
      AddKeyboardShortcut(0, #PB_Shortcut_Escape, 8)
      AddKeyboardShortcut(0, #PB_Shortcut_Control | #PB_Shortcut_Q, 5)
      thr1\gadStb = #gadStb
      Repeat
        Select WaitWindowEvent()
        Case #PB_Event_Menu   ;-!1. Menu Events
          evM = EventMenu()
          Select evM
          Case #mnuQuit
            Break
          Case #mnuThrRun
            gui_thr_run()
          Case #mnuThrPause_Resume
            gui_thr_pause()
          Case #mnuThrStop
            gui_thr_stop()
            SetGadgetText(#gadPause, "Pause")
          EndSelect
        Case #thr_event_now   ;-!2. Thread Events
          Select thr1\now
          Case #thr_now_none
            gui_thr_update("thr_event_now - none")
            StatusBarText(thr1\gadStb, 0, "None")
          Case #thr_now_running
            gui_thr_update("thr_event_now - running")
            StatusBarText(thr1\gadStb, 0, "Running")
            SignalSemaphore(thr1\sig)
          Case #thr_now_paused
            gui_thr_update("thr_event_now - paused")
            StatusBarText(thr1\gadStb, 0, "Paused")
          Case #thr_now_stopped
            gui_thr_update("thr_event_now - stopped")
            StatusBarText(thr1\gadStb, 0, "Stopped")
          Case #thr_now_done
            gui_thr_update("thr_event_now - done")
            StatusBarText(thr1\gadStb, 0, "Done")
            thr1\n = 0
          EndSelect
        Case #PB_Event_Gadget
          Select EventGadget()     ;-!3. Gadget Events
          Case #gadRun
            gui_thr_run()
          Case #gadPause
            gui_thr_pause()
          Case #gadStop
            gui_thr_stop()
          EndSelect
        Case #PB_Event_CloseWindow
          gui_thr_stop()
          WaitThread(thr1, 3000)
          Delay(30)
          Break
        EndSelect
      ForEver
    EndIf
  EndProcedure
  Main()
CompilerEndIf
;-} TEST
Last edited by skywalk on Tue Aug 22, 2017 3:03 am, edited 1 time in total.
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Post Reply