Threads and Procedures
Re: Threads and Procedures
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
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Re: Threads and Procedures
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.
Error in line 845. Disabled the highlighting script in the browser, it failed.
Thanks, the examples work well.
Re: Threads and Procedures
@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.
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) )
Re: Threads and Procedures
My take... the B-GAP approach:
(depends on the complexity of the application...)
Basic:
(depends on the complexity of the application...)
Basic:
Good: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)
Advanced: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
Paranoia:8. Use a supervisor to watch over the health of the threads
9. The supervisor kills or restarts threads as needed
10. A supervisor watches only over a group of threads
11. A master supervisor watches over all the other supervisors

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