Threads and Procedures

Just starting out? Need help? Post your questions and find answers here.
User avatar
mk-soft
Always Here
Always Here
Posts: 6205
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Threads and Procedures

Post by mk-soft »

See Signatur - ThreadToGUI
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
AZJIO
Addict
Addict
Posts: 2143
Joined: Sun May 14, 2017 1:48 am

Re: Threads and Procedures

Post by AZJIO »

jacdelad wrote: Mon Aug 01, 2022 9:40 pm Hm, ok, I only use Windows.
the Linux label means that it works without a thread, otherwise the sources are the same. The source works on Windows, but will not output to the status bar.
mk-soft wrote: Mon Aug 01, 2022 9:59 pm See Signatur - ThreadToGUI
Error in line 845. Disabled the highlighting script in the browser, it failed.
Thanks, the examples work well.
Olli
Addict
Addict
Posts: 1200
Joined: Wed May 27, 2020 12:26 pm

Re: Threads and Procedures

Post by Olli »

@AZJIO

I consider the threads and the processes, like any clients and any servers, exchanging between themselves, through a common structured memory.

This code is 13 years old, and, excepted the constants values which changed from a compiler version to another one, I think it is okay. Anyway, sure it is okay on the official pb compiler in 2009. Just a '16' which be replaced with '#PB_Event_CloseWindow', the UNICODE type to be used now, etc... to update for today. Anyway, the compiler of 2009 is ever available to test directly.

You can observe all the rules written above are respected, and more again : nothing else the CPU ops are in the thread.

The common memory is structured through "Alarm" structure, we can read first.

If a progressing value had to be used, we just have to insert a field in this structure.

Code: Select all

EnableExplicit

Structure Thread
   *Root
   *MessageRequester
   *InputRequester
   *CreateRequester
   Title.S
   Message.S
   InputString.S
   *Command
   *Result
   CIndex.L
   No.L[16]
   *Mem[16]
   Qty.L
EndStructure

Structure Requester
   Command.L
   Win.L
   MessageGdt.L   
   InputGdt.L
   *Result
   OkGdt.L
   Event.L
   EventGdt.L
EndStructure

Structure Alarm
   *Root
   *ResetAll
   *CheckSyntax
   *Ack
   *AckAll
   *InputDate
   *CheckAll
   *CloseAll
   *AvailableOne
   *Set
   *Reset
   *NewOne
   Start.L
   ListWin.L
   ListGdt.L
   AckGdt.L
   Menu.L
   MenuItem.L
   Event.L
   EventGdt.L
   EventType.L
   HourSet.S
   StatSet.L
   DateSys.L
   HmsSys.S
   HourSys.L
   MinuteSys.L
   SecondSys.L
   MaxQty.L
   Index.L
   DateError.L
   RequestFlag.L
   *Hours
EndStructure


Structure AlarmSet
   Ymd.S
   Year.L
   Month.L
   Day.L
   Hms.S
   Hour.L
   Minute.L
   Second.L
   Stat.L
EndStructure


Structure AlarmTable
   Get.AlarmSet[256]
EndStructure

Structure Root
   *Requester
   *Alarm
   ShutDown.L
EndStructure

Procedure ThreadRequester(*C.Thread)
   Protected *M.Requester
   Protected *ResultString
   With *C
      *M = AllocateMemory(SizeOf(Requester) )
      *C\Mem[\CIndex] = *M
      *M\Command = \Command
      *M\Result = \Result
      *M\Win = OpenWindow(-1, 0, 0, 384, 128, \Title, $CF0001)
      *M\MessageGdt = TextGadget(-1, 16, 16, 352, 24, \Message, 1)
      If \Command = \InputRequester
         *M\InputGdt = StringGadget(-1, 16, 56, 352, 24, "")
         *M\OkGdt = ButtonGadget(-1, 96, 96, 192, 24, "Ok")      
      Else
         *M\OkGdt = ButtonGadget(-1, 96, 56, 192, 24, "Ok")      
         ResizeWindow(*M\Win, #PB_Ignore, #PB_Ignore, 384, 88)
      EndIf
      Repeat
         *M\Event = WaitWindowEvent()
         Select *M\Event
            Case #PB_Event_Gadget
               *M\EventGdt = EventGadget()
               Select *M\EventGdt
                  Case *M\OkGdt
                     If *M\Command = \InputRequester
                        \InputString = GetGadgetText(*M\InputGdt)
                        *ResultString = AllocateMemory(Len(\InputString) + 1)
                        PokeS(*ResultString, \InputString)
                        PokeL(\Result, *ResultString) ; /!\ Pointeur 32 bits
                     EndIf
                     *M\Event = 16
               EndSelect   
         EndSelect           
      Until *M\Event = 16
      CloseWindow(*M\Win)   
      FreeMemory(*M)
   EndWith   
EndProcedure

Procedure ThreadMessageRequester(*C.Thread)
   ThreadRequester(*C)
   Delay(500)
EndProcedure

Procedure ThreadInputRequester(*C.Thread)
   ThreadRequester(*C)
   Delay(500)
EndProcedure

Procedure StartRequesterThread(*C.Thread)
   Protected Index.L
   For Index = 0 To 15
      If IsThread(*C\No[Index]) = 0
         *C\CIndex = Index
         *C\No[Index] = CreateThread(*C\Command, *C)
         Break
      EndIf
   Next Index
   Repeat
      Delay(50)
   Until IsThread(*C\No[Index])
EndProcedure

Procedure RequesterMain(*Root.Root)
   Protected Index.L
   Protected Thread.Thread
   Protected *MyString1
   *Root\Requester = Thread
   With Thread
      \MessageRequester = @ThreadMessageRequester()
      \InputRequester = @ThreadInputRequester()
      Repeat
         Delay(100)
      Until *Root\ShutDown
   EndWith   
EndProcedure   




Procedure AlarmResetAll(*C.Alarm)
   Protected Index.L
   Protected i.L
   With *C
      *C\RequestFlag = 1
      \ListWin = OpenWindow(-1, 0, 0, 256, 384, "Alarmes", $CF0001)
      \ListGdt = ListIconGadget(-1, 16, 16, 224, 312, "N°", 32, #PB_ListIcon_FullRowSelect)
      \AckGdt = ButtonGadget(-1, 16, 342, 224, 24, "Acquitter toutes les alarmes signalées")
      AddGadgetColumn(\ListGdt, 1, "Horaires", 128)
      AddGadgetColumn(\ListGdt, 2, "Etat", 64)
      For Index = 0 To \MaxQty - 1
         AddGadgetItem(\ListGdt, Index, Str(Index) )
      Next Index
      \Hours = AllocateMemory(SizeOf(AlarmSet) * \MaxQty)
      \Menu = CreatePopupMenu(-1)
      MenuItem(1, "Activer")
      MenuItem(2, "Désactiver")
      MenuItem(3, "Acquitter")
      MenuBar()
      MenuItem(4, "Modifier l'horaire") 
      MenuBar()
      MenuItem(7, "Quitter")
      For i = 0 To 500
         WindowEvent()
      Next
   EndWith
EndProcedure



Procedure AlarmSet(*C.Alarm)
   Protected Index.L
   Protected *AlarmTable.AlarmTable
   With *AlarmTable\Get[Index]
      SetGadgetItemText(*C\ListGdt, *C\Index, Str(*C\Index), 0)
      SetGadgetItemText(*C\ListGdt, *C\Index, *C\HourSet, 1)
      *AlarmTable = *C\Hours
      Index = *C\Index
      \Ymd = Left(*C\HourSet, 8)
      \Year = Val(StringField(\Ymd, 3, "/") )
      \Month = Val(StringField(\Ymd, 2, "/") )
      \Day = Val(StringField(\Ymd, 1, "/") )
      \Hms = Right(*C\HourSet, 8)
      \Hour = Val(StringField(\Hms, 1, ":") )
      \Minute = Val(StringField(\Hms, 2, ":") )
      \Second = Val(StringField(\Hms, 3, ":") )
      \Stat = *C\StatSet
   EndWith
EndProcedure



Procedure AlarmCheckSyntax(*C.Alarm)
   Protected Error.L
   Protected S.S
   Protected i.L
   Protected j.L
   Protected a.L
   Protected *Root.Root
   Protected *Req.Thread
   *Root = *C\Root
   *Req = *Root\Requester
   *C\DateError = 1
   S = Trim(*C\HourSet)
   S = Left(S, 8) + " " + Right(S, 8)
   If Len(S) = 17
      If Mid(S, 3, 1) = "/"
         If Mid(S, 6, 1) = "/"
            If Mid(S, 9, 1) = " "
               If Mid(S, 12, 1) = ":"
                  If Mid(S, 15, 1) = ":"
                     *C\DateError = 0
                     For i = 1 To 16 Step 3
                        For j = 0 To 1
                           a = Asc(Mid(S, i + j, 1) )
                           If (a < 48) Or (a > 57)
                              *C\DateError = 1
                           EndIf
                        Next j
                     Next i   
                  EndIf
               EndIf
            EndIf
         EndIf
      EndIf 
   EndIf
   If *C\DateError
      If *C\RequestFlag
         *Req\Command = *Req\MessageRequester
         *Req\Title = "Erreur"
         *Req\Message = "La syntaxe de la date est invalide !"
         StartRequesterThread(*Req)
      EndIf
      *C\HourSet = ""
      *C\StatSet = 0
   Else
      *C\HourSet = S
   EndIf
EndProcedure



Procedure AlarmAck(*C.Alarm)
   Protected DateSet.L
   Protected *AlarmTable.AlarmTable
   *AlarmTable = *C\Hours
   With *AlarmTable\Get[*C\Index]
      *C\HourSet = \Ymd + " " + \Hms
      DateSet = ParseDate("%dd/%mm/%yy %hh:%ii:%ss", *C\HourSet)
      DateSet + 86400               
      *C\HourSet = FormatDate("%dd/%mm/%yy %hh:%ii:%ss", DateSet)
   EndWith
   *C\StatSet = 1
   AlarmSet(*C)                        
EndProcedure



Procedure AlarmAckAll(*C.Alarm)
   Protected Index.L
   Protected DateSet.L
   Protected *AlarmTable.AlarmTable
   With *C
      *AlarmTable = \Hours
      For Index = 0 To \MaxQty - 1
         If *AlarmTable\Get[Index]\Stat = 2
            EndWith
            With *AlarmTable\Get[Index]
               *C\HourSet = \Ymd + " " + \Hms
               DateSet = ParseDate("%dd/%mm/%yy %hh:%ii:%ss", *C\HourSet)
               DateSet + 86400               
               *C\HourSet = FormatDate("%dd/%mm/%yy %hh:%ii:%ss", DateSet)
            EndWith
            With *C
            \Index = Index
            \StatSet = 1
            AlarmSet(*C)                        
         EndIf
      Next Index
   EndWith
EndProcedure



Procedure AlarmInputDate(*C.Alarm)
   Protected *Root.Root
   Protected *Req.Thread
   *Root = *C\Root
   *Req = *Root\Requester
   Protected MyString1.L
   Protected *AlarmTable.AlarmTable
   With *AlarmTable\Get[*C\Index]
      *Req\Command = *Req\InputRequester
      *Req\Title = "Modifier l'horaire"
      *Req\Message = "Respectez la syntaxe JJ/MM/AA HH:MM:SS"
      *Req\Result = @MyString1
      StartRequesterThread(*Req)
      Repeat
         Delay(500)
      Until MyString1 <> 0
      *C\HourSet = PeekS(MyString1)
      FreeMemory(MyString1)
      AlarmCheckSyntax(*C)
      AlarmSet(*C)
   EndWith      
EndProcedure



Procedure AlarmCheckAll(*C.Alarm)
   Protected *AlarmTable.AlarmTable
   Protected Index.L
   Protected Field.S
   Protected AlarmValue.L
   Protected i.L
   Protected *Root.Root
   Protected *Req.Thread
   *Root = *C\Root
   *Req = *Root\Requester
   *AlarmTable = *C\Hours   
   Delay(500)
      Repeat
         With *C
            \DateSys = Date()
            \HourSys = Hour(\DateSys)
            \MinuteSys = Minute(\DateSys)
            \SecondSys = Second(\DateSys)
            \HmsSys = RSet(Str(\HourSys ), 2, "0") + ":"
            \HmsSys + RSet(Str(\MinuteSys), 2, "0") + ":"
            \HmsSys + RSet(Str(\SecondSys), 2, "0")
            SetWindowTitle(\ListWin, \HmsSys)
         EndWith
         For Index = 0 To *C\MaxQty - 1
            With *AlarmTable\Get[Index]
               Select \Stat
                  Case 1
                     Field = "Active"
                     If *C\Start = 0
                        AlarmValue = ParseDate("%dd/%mm/%yy %hh:%ii:%ss", \Ymd + " " + \Hms)
                        If AlarmValue <= *C\DateSys
                           \Stat = 2
                           *Req\Command = *Req\MessageRequester
                           *Req\Title = "Avertissement"
                           *Req\Message = "Alarme n°" + Str(Index) + " !"
                           StartRequesterThread(*Req)
                           *C\Start = 1
                        EndIf                        
                     EndIf          
                  Case 0
                     Field = "Inactive"
                  Case 2
                     Field = "Signalée"
               EndSelect
               SetGadgetItemText(*C\ListGdt, Index, Field, 2)
            EndWith      
         Next Index
         *C\Start = 0
         Delay(25)
         *C\Event = WindowEvent()
         If *C\Event = #PB_Event_Gadget
            *C\EventGdt = EventGadget()
            *C\EventType = EventType()
            Select *C\EventGdt
               Case *C\AckGdt
                  AlarmAckAll(*C)
               Case *C\ListGdt
                  If *C\EventType = #PB_EventType_RightClick
                     *C\Index = GetGadgetState(*C\ListGdt)
                     If *C\Index <> -1
                        With *AlarmTable\Get[*C\Index]
                           For i = 1 To 3
                              DisableMenuItem(*C\Menu, i, 1)
                              DisableMenuItem(*C\Menu, i + 3, 1)
                           Next i
                           DisableMenuItem(*C\Menu, 4, 0)
                           If \Stat = 0
                              *C\HourSet = \Ymd + " " + \Hms
                              *C\RequestFlag = 0
                              AlarmCheckSyntax(*C)
                              *C\RequestFlag = 1
                              If *C\DateError = 0
                                 DisableMenuItem(*C\Menu, 1, 0)
                              EndIf
                           EndIf
                           If \Stat = 1
                              DisableMenuItem(*C\Menu, 2, 0)
                           EndIf
                           If \Stat = 2
                              DisableMenuItem(*C\Menu, 3, 0)
                           EndIf
                        EndWith
                        DisplayPopupMenu(*C\Menu, WindowID(*C\ListWin) )
                     EndIf
                  EndIf
            EndSelect
         EndIf
         If *C\Event = #PB_Event_Menu
            *C\MenuItem = EventMenu()
            With *AlarmTable\Get[*C\Index]
            Select *C\MenuItem
               Case 1
                  \Stat = 1
               Case 2
                  \Stat = 0
               Case 3
                  AlarmAck(*C)
               Case 4
                  AlarmInputDate(*C)
               Case 7
                  *C\Event = 16
            EndSelect
            EndWith                  
         EndIf
      Until *C\Event = 16
EndProcedure



Procedure AlarmCloseAll(*C.Alarm)
   With *C
      CloseWindow(\ListWin)
      FreeMemory(\Hours)
   EndWith
EndProcedure



Procedure AlarmAvailableOne(*C.Alarm)
   Protected Index.L
   Protected *AlarmTable.AlarmTable
   With *C
      *AlarmTable = \Hours
      For Index = 0 To \MaxQty - 1
         If *AlarmTable\Get[Index]\Hms = ""
            \Index = Index
            Break
         EndIf
      Next Index
   EndWith
EndProcedure



Procedure AlarmReset(*C.Alarm)
   Protected Index.L
   Protected *AlarmTable.AlarmTable
   With *AlarmTable
      *AlarmTable = *C\Hours
      Index = *C\Index
      \Get[Index]\Stat = 0
   EndWith
EndProcedure



Procedure AlarmNewOne(*C.Alarm)
   With *C
      AlarmAvailableOne(*C)
      AlarmSet(*C)
   EndWith
EndProcedure



Procedure AlarmMain(*Root.Root)
   Protected Alarm.Alarm
   Alarm\Root = *Root
   *Root\Alarm = Alarm
   Alarm\ResetAll = @AlarmResetAll()
   Alarm\Set = @AlarmSet()
   Alarm\CheckSyntax = @AlarmCheckSyntax()
   Alarm\Ack = @AlarmAck()
   Alarm\AckAll = @AlarmAckAll()
   Alarm\InputDate = @AlarmInputDate()
   Alarm\CheckAll = @AlarmCheckAll()
   Alarm\CloseAll = @AlarmCloseAll()
   Alarm\AvailableOne = @AlarmAvailableOne()
   Alarm\Reset = @AlarmReset()
   Alarm\NewOne = @AlarmNewOne()
   ; Initialisation
   Alarm\MaxQty = 16 ; Nombre max d'alarmes
   AlarmResetAll(Alarm)
   
   ; On rentre une première alarme inactive
   Alarm\HourSet = "21/02/09 21:02:00"
   Alarm\StatSet = 0
   AlarmNewOne(Alarm)

   ; On rentre une seconde alarme active
   Alarm\StatSet = 1
   Alarm\HourSet = "21/02/09 20:03:00"
   AlarmNewOne(Alarm)
   
   ; On rentre une seconde alarme signalée
   Alarm\StatSet = 2
   Alarm\HourSet = "21/02/09 20:40:00"
   AlarmNewOne(Alarm)
   
   ; On rentre une seconde alarme signalée
   Alarm\StatSet = 1
   Alarm\HourSet = "20/02/09 23:20:00"
   AlarmNewOne(Alarm)
   
   Alarm\Start = 1
   AlarmCheckAll(Alarm)

   AlarmCloseAll(Alarm)
   *Root\ShutDown = 1
EndProcedure



   Define Root.Root
   CreateThread(@RequesterMain(), Root)
   WaitThread(CreateThread(@AlarmMain(), Root) )
   
User avatar
fsw
Addict
Addict
Posts: 1603
Joined: Tue Apr 29, 2003 9:18 pm
Location: North by Northwest

Re: Threads and Procedures

Post by fsw »

My take... the B-GAP approach:
(depends on the complexity of the application...)

Basic:
mk-soft wrote: Sat Jul 30, 2022 6:27 pm 1. Use always compiler option thread-safe
2. Do never change GUI objects from thread (crash)
3. Exiting Threads before end program
4. Use not KillThread for exiting thread (Only if thread hangs)
Good:
5. Let only one thread (usually the main thread) access the data directly
6. Avoid letting other threads write data directly
7. Use messages to communicate what needs to be done with the data
Advanced:
8. Use a supervisor to watch over the health of the threads
9. The supervisor kills or restarts threads as needed
Paranoia:
10. A supervisor watches only over a group of threads
11. A master supervisor watches over all the other supervisors
:mrgreen:

I am to provide the public with beneficial shocks.
Alfred Hitshock
Post Reply