Re: Threads and Procedures
Posted: Mon Aug 01, 2022 9:59 pm
See Signatur - ThreadToGUI
http://www.purebasic.com
https://www.purebasic.fr/english/
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.
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) )
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