Page 1 of 1

Timer question using threads

Posted: Tue Aug 15, 2006 6:51 pm
by Character
I've written this simple timer to avoid a burning disaster in my kitchen.
After testing this code (could be better code, I know :oops: ) a very strange thing occurs:
Most of the time it works fine but then randomly when clicking on the start button the timer goes off immediately and the timer doesn't run.
Can it be that I overlook something or have I misunderstood the thread thing. (I enabled threadsafe when compiling)


Most of the code:

Code: Select all


;- Window Constants
Enumeration
  #Window_0
  #Window_1
EndEnumeration

;- Gadget Constants
Enumeration
  #Button_0
  #Button_1
  #Frame3D_2
  #Combo_0
  #Image_0
  #Sound_0
  #Sound_1
  #ProgressBar_0
  #Text_1
  #Text_2
  #Text_3
  #Editor_1
EndEnumeration

;- Fonts Global
Global FontID1
FontID1 = LoadFont(1, "Lucida Console", 72)
Global FontID2
FontID2 = LoadFont(2, "Lucida Console", 10, #PB_Font_Bold)
Global FontID3
FontID3 = LoadFont(3, "Lucida Console", 8)

;- Image Global
Global Image0

;- Catch Image
Image0 = CatchImage(0, ?Image0)

;- Image Inclusion
DataSection
Image0:
  IncludeBinary "Beeld\Klok.ico"
EndDataSection

;- Sound Global
Global Sound0
Global Sound1

;- Catch Sound
If InitSound()=0 
  MessageRequester("Fout audio systeem","Audio omgeving kan niet geïnitaliseerd worden.", #MB_ICONERROR)
  End
EndIf  
Sound0 = CatchSound(#Sound_0,?Sound0)
Sound1 = CatchSound(#Sound_1,?Sound1)

;- Sound Inclusion
DataSection
Sound0: 
  IncludeBinary "Geluid\Crick.wav"
Sound1:
  IncludeBinary "Geluid\Pinc.wav"
EndDataSection

;- Globals & Declarations
Global Insteltijd.s
Global Wekkerklaar.b=0
Global Wekkermin.b=0
Global Wekkersec.b=0
Global Totaalsec.w=0
Global Voortgang.f=0
Global Alarmstop.b=0

;- Thread Global 
Global WekkertjeThreadID


;- Procedures
Procedure Open_Window_0()
  If OpenWindow(#Window_0, 438, 291, 250, 200, "'t Kook Wekkertje ",  #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_TitleBar | #PB_Window_ScreenCentered )
    SetWindowPos_(WindowID(#Window_0), #HWND_TOPMOST, 0, 0, 0, 0, #SWP_NOMOVE | #SWP_NOSIZE)
    If CreateGadgetList(WindowID(#Window_0))
      ButtonGadget(#Button_0, 10, 170, 170, 20, ">> <<")
      ButtonGadget(#Button_1, 190, 170, 50, 20, "?")
      SetGadgetFont(#Button_1, FontID3)
      Frame3DGadget(#Frame3D_2, 10, 10, 170, 150, "", #PB_Frame3D_Single)
      ComboBoxGadget(#Combo_0, 190, 60, 50, 20, #PB_ComboBox_LowerCase)
      ImageGadget(#Image_0, 200, 10, 32, 32, Image0)
      ProgressBarGadget(#ProgressBar_0, 210, 100, 10, 60, 0, 10, #PB_ProgressBar_Vertical)
      AddGadgetItem(#Combo_0,-1,"1")
      For Tijd.b = 2 To 60
        AddGadgetItem(#Combo_0,-1,Str(Tijd))
      Next     
      SetGadgetState(#Combo_0, 0)
      TextGadget(#Text_1, 30, 30, 130, 80, "00", #PB_Text_Center)
      SetGadgetFont(#Text_1, FontID1)
      TextGadget(#Text_2, 150, 140, 20, 10, "00", #PB_Text_Right)
      SetGadgetFont(#Text_2, FontID2)
      TextGadget(#Text_3, 20, 140, 50, 10, "0 min")
      SetGadgetFont(#Text_3, FontID3)
    EndIf
  EndIf
EndProcedure

Procedure Wekkertje_Start(Unused)
  For Wekkermin = 1 To Val(Insteltijd)
    For Wekkersec = 1 To 60
      Delay(1000)
      If Wekkersec<10 : Wekkerseconde.s="0"+Str(Wekkersec) : Else : Wekkerseconde=Str(Wekkersec) : EndIf
      If Wekkersec<60 
        SetGadgetText(#Text_2, Wekkerseconde)
      ElseIf Wekkersec=60
        SetGadgetText(#Text_2, "00")
      EndIf  
      SetGadgetFont(#Text_2, FontID2)
      Totaalsec+1
      Voortgang=(Totaalsec/(Val(Insteltijd)*60))*10
      SetGadgetState(#ProgressBar_0, Int(Voortgang))
    Next
    If Wekkermin<10 : Wekkerminuut.s="0"+Str(Wekkermin) : Else : Wekkerminuut=Str(Wekkermin) : EndIf
    SetGadgetText(#Text_1, Wekkerminuut)
    SetGadgetFont(#Text_1, FontID1)
  Next
  Wekkerklaar=1
  For Lokaal.b=1 To 120
    If Alarmstop=1
      Alarmstop=0
      Break
    EndIf
    PlaySound(#Sound_1,0)
    Delay(500)
  Next
EndProcedure

Procedure Wekkertje_Stop(Unused)
  Wekkermin=1
  Wekkersec=0
  Totaalsec=0
  SetGadgetText(#Text_1, "00")
  SetGadgetFont(#Text_1, FontID1)
  SetGadgetText(#Text_2, "00")
  SetGadgetFont(#Text_2, FontID2)
  SetGadgetState(#ProgressBar_0, 0)
EndProcedure
;- Set Thread
WekkertjeThreadID=CreateThread(@Wekkertje_Start(),0)
If WekkertjeThreadID=0 
  MessageRequester("Fout in thread aanroep", "Kan de klok in het programma niet weergeven door een threading fout.",  #MB_ICONERROR)
EndIf
PauseThread(WekkertjeThreadID)


;- Main
Knoptrigger.b=0
Open_Window_0()

;- Loop
Repeat 
  
  Event = WaitWindowEvent() ; This line waits until an event is received from Windows
  WindowID = EventWindow() ; The Window where the event is generated, can be used in the gadget procedures  
  GadgetID = EventGadget() ; Is it a gadget event?
  EventType = EventType() ; The event type
    
  If Event = #PB_Event_Gadget
    
    If GadgetID = #Button_0
      If Knoptrigger=0
        If Wekkerklaar=0
          Delay(10)
          ResumeThread(WekkertjeThreadID)
        ElseIf Wekkerklaar =1 
          WekkertjeThreadID=CreateThread(@Wekkertje_Start(),0)
          Wekkerklaar=0
        EndIf
        Insteltijd=GetGadgetText(#Combo_0)
        DisableGadget(#Combo_0, 1)
        SetGadgetText(#Text_3,  Insteltijd+" min")
        SetGadgetFont(#Text_3, FontID3)
        Knoptrigger=1
        PlaySound(#Sound_0,0)
      ElseIf Knoptrigger=1
        If Wekkerklaar=0
          PauseThread(WekkertjeThreadID)
        EndIf
        If Wekkerklaar=1
          Alarmstop=1
        EndIf  
        DisableGadget(#Combo_0, 0)
        Wekkertje_Stop(Unused)
        SetGadgetText(#Text_3,  "0 min")
        SetGadgetFont(#Text_3, FontID3)
        Knoptrigger=0
        PlaySound(#Sound_0,0)
      EndIf
      
    ElseIf GadgetID = #Button_1
      HulpWindow(Terug) 
    ElseIf GadgetID = #Combo_0      
    ElseIf GadgetID = #Image_0
    ElseIf GadgetID = #ProgressBar_0
      
    EndIf
    
  EndIf
  
Until Event = #PB_Event_CloseWindow 

End

Posted: Tue Aug 15, 2006 7:51 pm
by netmaestro
It looks like you're starting the thread at program start and then pausing it until it's needed, is that right? I wouldn't do it that way, I'd just start the thread when it's needed.

Posted: Tue Aug 15, 2006 8:11 pm
by dracflamloc
I'd just start this thread when your start button is pressed:

Code: Select all

Procedure TimerThread(minutes.l)

minutes * 2
while minutes > 0
  Delay(30000)
  ;you can update time left or whatever here
  minutes - 1
wend
;bing bing!
MessageRequester("Alarm","")

Endprocedure 
Havent actually tested that but it should work.

Posted: Tue Aug 15, 2006 8:23 pm
by Xombie
I messed around with a non-thread solution. See if this works for you?

Code: Select all

Enumeration ; Window List 
   #WindowMain 
EndEnumeration 
Enumeration ; Control List 
   #ButtonActivity
   #ButtonHelp
   #FrameTime
   #ComboTime
   #ProgressTime
   #TextMinutesPassed
   #TextMinutesRemaining
   #TextSecondCount
EndEnumeration 
;-
EnableExplicit
;-
Structure _s_Main
   FontMinutesPassed.l
   FontMinutesCount.l
   FontSecondsCount.l
   CallBack.l
   CountTime.l
   MaxTime.l
EndStructure
;-
Global _Timer_Main._s_Main
_Timer_Main\FontMinutesPassed = LoadFont(0, "Lucida Console", 72)
_Timer_Main\FontMinutesCount = LoadFont(1, "Lucida Console", 10, #PB_Font_Bold)
_Timer_Main\FontSecondsCount = LoadFont(2, "Lucida Console", 8)
;-
Procedure TimerCallback(Handle.l, uMsg.l, idEvent.l, dwTime.l)
   ;
   Define.l HoldTime
   ;
   ;/ You could play a ticking sound here if you want.
   ;
   _Timer_Main\CountTime + 1
   ;
   SetGadgetText(#TextSecondCount, RSet(Str(_Timer_Main\CountTime % 60), 2, "0"))
   ;
   If _Timer_Main\CountTime % 60 = 0
      ;
      SetGadgetText(#TextMinutesPassed, RSet(Str(Int(_Timer_Main\CountTime / 60)), 2, "0"))
      ;
      HoldTime = Int((_Timer_Main\MaxTime - _Timer_Main\CountTime) / 60)
      ;
      If HoldTime > 1
         ;
         SetGadgetText(#TextMinutesRemaining, Str(HoldTime)+" mins")
         ;
      Else
         ;
         SetGadgetText(#TextMinutesRemaining, "1 min")
         ;
      EndIf
      ;
   EndIf
   ;
   SetGadgetState(#ProgressTime, Int(_Timer_Main\CountTime / _Timer_Main\MaxTime * 10))
   ;
   If _Timer_Main\CountTime = _Timer_Main\MaxTime
      ;
      ;/ Play finished sound here
      ;
      KillTimer_(WindowID(#WindowMain), 0)
      ;
      _Timer_Main\CallBack = 0
      ;
      _Timer_Main\CountTime = 0
      ;
      SetGadgetState(#ButtonActivity, 0)
      ;
      ;/ Reset the text boxes if you like
      ;
   EndIf
   ;
EndProcedure
;-
Define.l iLoop
;
Define.b DoQuit
;
Define.l HoldEvent
;
Define.l HoldTime
;
If OpenWindow(#WindowMain, 438, 291, 250, 200, "Timer", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget) 
   ; 
   SetWindowPos_(WindowID(#WindowMain), #HWND_TOPMOST, 0, 0, 0, 0, #SWP_NOMOVE | #SWP_NOSIZE) 
   ;
   If CreateGadgetList(WindowID(#WindowMain)) 
      ButtonGadget(#ButtonActivity, 10, 170, 170, 20, ">> <<", #PB_Button_Toggle) 
      ButtonGadget(#ButtonHelp, 190, 170, 50, 20, "?") 
         SetGadgetFont(#ButtonHelp, _Timer_Main\FontSecondsCount) 
      Frame3DGadget(#FrameTime, 10, 10, 170, 150, "", #PB_Frame3D_Single) 
      ComboBoxGadget(#ComboTime, 190, 60, 50, 200) 
         For iLoop = 1 To 60 : AddGadgetItem(#ComboTime,-1,Str(iLoop)) : Next      
         SetGadgetState(#ComboTime, 0) 
      ProgressBarGadget(#ProgressTime, 210, 100, 10, 60, 0, 10, #PB_ProgressBar_Vertical) 
      TextGadget(#TextMinutesPassed, 30, 30, 130, 80, "00", #PB_Text_Center) 
         SetGadgetFont(#TextMinutesPassed, _Timer_Main\FontMinutesPassed) 
      TextGadget(#TextSecondCount, 150, 140, 20, 10, "00", #PB_Text_Right) 
         SetGadgetFont(#TextSecondCount, _Timer_Main\FontMinutesCount) 
      TextGadget(#TextMinutesRemaining, 20, 140, 50, 10, "0 min") 
         SetGadgetFont(#TextMinutesRemaining, _Timer_Main\FontSecondsCount) 
   EndIf 
   ;
   While DoQuit = #False
      ; 
      HoldEvent = WaitWindowEvent() 
      ; 
      If HoldEvent = #PB_Event_CloseWindow
         ; 
         DoQuit = #True 
         ; 
      ElseIf HoldEvent = #PB_Event_Gadget 
         ; 
         If EventGadget() = #ButtonActivity
            ; 
            If EventType() = #PB_EventType_LeftClick
               ;
               If _Timer_Main\CallBack
                  ;
                  KillTimer_(WindowID(#WindowMain), 0)
                  ;
                  _Timer_Main\CallBack = 0
                  ;
                  _Timer_Main\CountTime = 0
                  ;
               Else
                  ;
                  If Val(GetGadgetText(#ComboTime))
                     ;
                     HoldTime = Val(GetGadgetText(#ComboTime))
                     ;
                     If HoldTime > 1
                        ;
                        SetGadgetText(#TextMinutesRemaining, Str(HoldTime)+" mins")
                        ;
                     Else
                        ;
                        SetGadgetText(#TextMinutesRemaining, "1 min")
                        ;
                     EndIf
                     ;
                     _Timer_Main\MaxTime = HoldTime * 60
                     ;
                     _Timer_Main\CallBack = SetTimer_(WindowID(#WindowMain), 0, 1000, @TimerCallback())
                     ;
                  Else
                     ;
                     SetGadgetState(#ButtonActivity, 0)
                     ;
                  EndIf
                  ;
               EndIf
               ;
            EndIf
           ;
            ;
         EndIf
         ;
      EndIf
      ;
   Wend
   ;
EndIf
;-
If _Timer_Main\CallBack : KillTimer_(WindowID(#WindowMain), 0) : EndIf
; Kill the timer if not dead already.
FreeFont(0) : FreeFont(1) : FreeFont(2)
; Free all custom fonts.
;-
End
;-
It doesn't have your sounds and images since I don't have them and don't know where you'd want them. Sorry about that ^_^

...: Edit :... I should say "non-thread but Windows only solution"

Posted: Tue Aug 15, 2006 8:42 pm
by netmaestro
I like the basic approach, but I'd suggest a tiny change: Set the timer frequency to around 50 ms and have the first line in the callback procedure test whether the system time has a new second or not. If not, skip everything. That way you're working from the system clock and you'll get accurate timing. If you set the frequency at 1000 and just assume that a second has gone by because the timer fired an event, each counted second will be out by a few milliseconds, which over longer timing periods can accumulate to a significant error.

Posted: Tue Aug 15, 2006 8:54 pm
by Trond
I'd say that if you can avoid threads, avoid them. Here's my thread-free timer:

Code: Select all

OpenWindow(0, 0, 0, 512, 384, "", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
CreateGadgetList(WindowID(0))
ButtonGadget(0, 10, 10, 100, 25, "Start timer")
TextGadget(1, 10, 40, 100, 22, "Seconds: ")
StringGadget(2, 60, 40, 100, 22, "10")

TextGadget(3, 100, 100, 100, 22, "")

StartTime = 0
Timer = -1
Sec = 0

Repeat
  Timeout = Timer <> -1 Or 0
  Select WaitWindowEvent(Timeout)
    Case #PB_Event_Gadget
      If EventGadget() = 0
        If Str(Val(GetGadgetText(2))) = GetGadgetText(2)
          StartTime = ElapsedMilliseconds()/1000
          Timer = Val(GetGadgetText(2))
          DisableGadget(0, 1)
        Else
          MessageRequester("", "invalid number")
        EndIf
      EndIf
    Case #PB_Event_CloseWindow
      Break
  EndSelect
  If Timer > -1
    If ElapsedMilliseconds()/1000 <> Sec
      Sec = ElapsedMilliseconds()/1000
      If ElapsedMilliseconds()/1000-StartTime = Timer
        MessageRequester("", "Time out!")
        Timer = -1
      EndIf
      SetGadgetText(3, Str(ElapsedMilliseconds()/1000-StartTime) + "/" + Str(Timer))
    EndIf
  EndIf
ForEver

just start the thread when it's needed

Posted: Tue Aug 15, 2006 8:57 pm
by Character
Thanks sofar everybody for the feedback. You're fast!

I'll try your tips top down and evaluate it.

I start with this one:

Code: Select all

netmaestro
It looks like you're starting the thread at program start and then pausing it until it's needed, is that right? I wouldn't do it that way, I'd just start the thread when it's needed.

Posted: Tue Aug 15, 2006 9:55 pm
by Character
netmaestro:
It looks like you're starting the thread at program start and then pausing it until it's needed, is that right? I wouldn't do it that way, I'd just start the thread when it's needed.
After compairing both app. versions (ResumeThread versus CallThread) 50 times the results are the following:
  • ResumeThread version (original) failed 3 times out of 50

    CallThread version failed 0 times out of 50
Have no idea what is the cause of this, although I'm glad I'm getting somewhere! :roll:


Is it really so bad using threads?
Is it possible to have a clock running while dragging the window around and also have the possibility to open another window on top of it?
That's why I ended @ threads in the first place.
[/quote]

Posted: Tue Aug 15, 2006 10:25 pm
by Xombie
I wouldn't say threads are bad. They just require a little more attention and care when using them. Otherwise, it's a programming style choice. I like to try and use the simplest solution possible as long as speed and/or memory is not sacrificed.

Threads seem a bit high-powered for what you want to do. You still have to have your own 'timing' function to make sure you're updating at the correct time. I like built-in timers for things like that. While it's not a high-performance solution (as netmaestro mentioned, there is loss of accuracy over long periods of time) it's still good enough for a simple cooking timer. If you were timing for data (sending data at very specific times) I'd recommend something else. For cooking, I'm thinking your soup won't mind if it's cooked for 5.03 seconds versus 5 seconds flat. To use an example. Also, the timer events are fired at intervals versus running your code in a loop. I like that a little better.

However, if you go with threads, I'd go with netmaestro and dracflamloc's suggestion to only start as soon as the button is pressed. Kill the thread or pause it when stopped.

Posted: Tue Aug 15, 2006 11:31 pm
by Character
Thanks for the extended information. Cool.

I don't understand all the examples yet but I will take my time.

ElapsedMilliseconds() thing is clear for me.
I will use this one instead of the delay(1000), although indeed for a cooking timer it is not that important.
Or to quote Kale in his PB book:
"I will use it for the sake of completeness"

Update timer

Posted: Sun Aug 27, 2006 11:58 pm
by Character
I got the thread 'thing' working.
Of course the code be written better but I'm still learning.
At least Kale got me now to the point that I will change some bad behaviors like:

:oops: Using Dutch names for variables.
:oops: Global seems to be my best friend.
:oops: No clear convention in capitalization.
:oops: Using random tabbed indent.
:oops: Making cryptograms instead of comments.
:oops: Using poor error handling
Etc..

Except for the content of a information window here is the complete code for my simple overkilled threaded timer.

Code: Select all


;- Mutex Creation
MutexID.l=CreateMutex_(0,1,"'t Kook Wekkertje")
MutexError.l=GetLastError_()
If MutexID=0 Or MutexError<>0
  ReleaseMutex_(MutexID)
  CloseHandle_(MutexID)
  End
EndIf

;- Window Constants
Enumeration
  #Window_0
  #Window_1
EndEnumeration

;- Gadget Constants
Enumeration
  #Button_0
  #Button_1
  #Frame3D_2
  #Combo_0
  #Image_0
  #Sound_0
  #Sound_1
  #ProgressBar_0
  #Text_1
  #Text_2
  #Text_3
  #Editor_1
EndEnumeration

;- Fonts Global
Global FontID1
FontID1 = LoadFont(1, "Lucida Console", 72)
Global FontID2
FontID2 = LoadFont(2, "Lucida Console", 10, #PB_Font_Bold)
Global FontID3
FontID3 = LoadFont(3, "Lucida Console", 8)

;- Image Global
Global Image0

;- Catch Image
Image0 = CatchImage(0, ?Image0)

;- Image Inclusion
DataSection
Image0:
  IncludeBinary "Beeld\Klok.ico"
EndDataSection

;- Sound Global
Global Sound0
Global Sound1

;- Catch Sound
If InitSound()=0 
  MessageRequester("Fout audio systeem","Audio omgeving kan niet geïnitaliseerd worden.", #MB_ICONERROR)
  End
EndIf  
Sound0 = CatchSound(#Sound_0,?Sound0)
Sound1 = CatchSound(#Sound_1,?Sound1)

;- Sound Inclusion
DataSection
Sound0: 
  IncludeBinary "Geluid\Crick.wav"
Sound1:
  IncludeBinary "Geluid\Pinc.wav"
EndDataSection

;- Globals & Declarations
Global Insteltijd.s
Global Wekkerklaar.b=0
Global Wekkermin.b=0
Global Wekkersec.b=0
Global Totaalsec.w=0
Global Voortgang.f=0
Global Alarmstop.b=0

;- Thread Global 
Global WekkertjeThreadID



;- Procedures
Procedure Open_Window_0()
  If OpenWindow(#Window_0, 438, 291, 250, 200, "'t Kook Wekkertje ",  #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_TitleBar | #PB_Window_ScreenCentered )
    SetWindowPos_(WindowID(#Window_0), #HWND_TOPMOST, 0, 0, 0, 0, #SWP_NOMOVE | #SWP_NOSIZE)
    If CreateGadgetList(WindowID(#Window_0))
      ButtonGadget(#Button_0, 10, 170, 170, 20, ">> <<")
      ButtonGadget(#Button_1, 190, 170, 50, 20, "?")
      SetGadgetFont(#Button_1, FontID3)
      Frame3DGadget(#Frame3D_2, 10, 10, 170, 150, "", #PB_Frame3D_Single)
      ComboBoxGadget(#Combo_0, 190, 60, 50, 20, #PB_ComboBox_LowerCase)
      ImageGadget(#Image_0, 200, 10, 32, 32, Image0)
      ProgressBarGadget(#ProgressBar_0, 210, 100, 10, 60, 0, 10, #PB_ProgressBar_Vertical)
      AddGadgetItem(#Combo_0,-1,"1")
      For Tijd.b = 2 To 60
        AddGadgetItem(#Combo_0,-1,Str(Tijd))
      Next     
      SetGadgetState(#Combo_0, 0)
      TextGadget(#Text_1, 30, 30, 130, 80, "00", #PB_Text_Center)
      SetGadgetFont(#Text_1, FontID1)
      TextGadget(#Text_2, 150, 140, 20, 10, "00", #PB_Text_Right)
      SetGadgetFont(#Text_2, FontID2)
      TextGadget(#Text_3, 20, 140, 50, 10, "0 min")
      SetGadgetFont(#Text_3, FontID3)
    EndIf
    Else
    MessageRequester("Fout","Venster kan niet geïnitaliseerd worden.", #MB_ICONERROR)
  EndIf
EndProcedure


Procedure HulpWindow(Terug)   
  If OpenWindow(#Window_1,0,0,500,300, "HELP en informatie", #PB_Window_SystemMenu | #PB_Window_ScreenCentered, WindowID(#Window_0))
    EnableWindow_(WindowID(#Window_0),#False)  ;win api to set focus on child window (so that the main window cannot be used)
    If CreateGadgetList(WindowID(#Window_1))
      EditorGadget(#Editor_1,5, 5, 490, 290, #PB_Editor_ReadOnly)
      SetGadgetFont(#Editor_1, FontID3)
      AddGadgetItem(#Editor_1,-1,"")
      AddGadgetItem(#Editor_1,-1,"'t Kook Wekkertje    (test versie 1.2)")
      AddGadgetItem(#Editor_1,-1,"____________________________________________________________")
    EndIf
  Else
  MessageRequester("Fout","Sub-venster kan niet geïnitaliseerd worden.", #MB_ICONERROR)
  EndIf
  
 Repeat
  Gebeurtenizz=WaitWindowEvent()
 Until Gebeurtenizz=#PB_Event_CloseWindow
 EnableWindow_(WindowID(#Window_0),#True)  ;Win api to set focus on main window (set focus back to main window before closing child window).
 CloseWindow(#Window_1)               
EndProcedure


Procedure Wekkertje_Start(Unused)
  For Wekkermin = 1 To Val(Insteltijd)
    For Wekkersec = 1 To 60
    
      ; Instead of Delay(1000).
      Eternity.q=ElapsedMilliseconds()
      If Eternity<2147483647 ; If over maximum value of a long integer return to Delay(1000).
        StartTime.l=ElapsedMilliseconds():While (ElapsedMilliseconds()-StartTime)<1000:Delay(1):Wend
      Else
        Delay(1000)
      EndIf
      
      If Wekkersec<10 : Wekkerseconde.s="0"+Str(Wekkersec) : Else : Wekkerseconde=Str(Wekkersec) : EndIf
      If Wekkersec<60 
        SetGadgetText(#Text_2, Wekkerseconde)
      ElseIf Wekkersec=60
        SetGadgetText(#Text_2, "00")
      EndIf  
      SetGadgetFont(#Text_2, FontID2)
      Totaalsec+1
      Voortgang=(Totaalsec/(Val(Insteltijd)*60))*10
      SetGadgetState(#ProgressBar_0, Int(Voortgang))
    Next
    If Wekkermin<10 : Wekkerminuut.s="0"+Str(Wekkermin) : Else : Wekkerminuut=Str(Wekkermin) : EndIf
    SetGadgetText(#Text_1, Wekkerminuut)
    SetGadgetFont(#Text_1, FontID1)
  Next
  Wekkerklaar=1
  For Lokaal.b=1 To 120
    If Alarmstop=1
      Alarmstop=0
      Break
    EndIf
    PlaySound(#Sound_1,0)
    Delay(500)
  Next
EndProcedure


Procedure Wekkertje_Stop(Unused)
  Wekkermin=1
  Wekkersec=0
  Totaalsec=0
  SetGadgetText(#Text_1, "00")
  SetGadgetFont(#Text_1, FontID1)
  SetGadgetText(#Text_2, "00")
  SetGadgetFont(#Text_2, FontID2)
  SetGadgetState(#ProgressBar_0, 0)
EndProcedure



;- MAIN
Knoptrigger.b=0
Open_Window_0()

;- Loop
Repeat 
  
  Event = WaitWindowEvent() ; This line waits until an event is received from Windows.
  WindowID = EventWindow() ; The Window where the event is generated. 
  GadgetID = EventGadget() ; Is it a gadget event?
  EventType = EventType() ; The event type.
    
  If Event = #PB_Event_Gadget
    
    If GadgetID = #Button_0
    
      If Knoptrigger=0
        If Wekkerklaar=0 
          Delay(10) ; I'm not sure if needed. Keep it there to hold one's horses.
          WekkertjeThreadID.l=CreateThread(@Wekkertje_Start(),0) ; First launch of the timer in a thread.
          If WekkertjeThreadID=0 
            MessageRequester("Fout in thread aanroep", "Kan de klok in het programma niet weergeven door een threading fout.",  #MB_ICONERROR)
            End
          EndIf
        ElseIf Wekkerklaar=1
          Wekkerklaar=0
          WekkertjeThreadID=CreateThread(@Wekkertje_Start(),0)
          If WekkertjeThreadID=0 
            MessageRequester("Fout in thread aanroep", "Kan de klok in het programma niet weergeven door een threading fout.",  #MB_ICONERROR)
            End
          EndIf
        EndIf
        Insteltijd=GetGadgetText(#Combo_0)
        DisableGadget(#Combo_0, 1)
        SetGadgetText(#Text_3,  Insteltijd+" min")
        SetGadgetFont(#Text_3, FontID3)
        Knoptrigger=1
        PlaySound(#Sound_0,0)
        
      ElseIf Knoptrigger=1
        If Wekkerklaar=0 
          PauseThread(WekkertjeThreadID)
        EndIf
        If Wekkerklaar=1 ; When button pressed, received through global from Wekkertje_Start() procedure.
          Alarmstop=1 ; Stops through global the alarm in the Wekkertje_Start() procedure. (see below)
        EndIf
        DisableGadget(#Combo_0, 0) ; No PauseThread() here because a Break statement already forced a thread (procedure) end.
        Wekkertje_Stop(Unused)
        SetGadgetText(#Text_3,  "0 min")
        SetGadgetFont(#Text_3, FontID3)
        Knoptrigger=0
        PlaySound(#Sound_0,0)
      EndIf
      
    ElseIf GadgetID = #Button_1
      HulpWindow(Terug) 
    ElseIf GadgetID = #Combo_0      
    ElseIf GadgetID = #Image_0
    ElseIf GadgetID = #ProgressBar_0
      
    EndIf
    
  EndIf
  
Until Event = #PB_Event_CloseWindow 

End

;- Mutex Close
CloseHandle_(MutexID)
End


Posted: Mon Aug 28, 2006 12:49 pm
by Baldrick
Here is some code for API based timers which I found somewhere in this forum some time back by Droopy, Kale & co. Just cant remember where the thread was.
The SetTimer_() & KillTimer_() API's are very handy.
Think you might find it very usefull for what you appear to be trying to do here.

Code: Select all

;/ Author: Droopy tweaked by Kale 

Procedure Timer1() 
   SetGadgetText(0,GetGadgetText(0)+"-") 
   Beep_(400,10) 
EndProcedure 

Procedure Timer2() 
   SetGadgetText(1,GetGadgetText(1)+"###") 
   Beep_(800,10) 
EndProcedure 

Procedure Timer3() 
   SetGadgetText(2,"Timer 1 and 2 Stopped") 
   KillTimer_(WindowID(0),1) ;/ Kill Timer #1 
   KillTimer_(WindowID(0),2) ;/ Kill Timer #2 
   Beep_(1500,500)
EndProcedure 

OpenWindow(0,0,0,230,120,"Timer with API",#PB_Window_SystemMenu|#PB_Window_ScreenCentered) 
   CreateGadgetList(WindowID(0)) 
      TextGadget(0,10,10,210,30,"") 
      TextGadget(1,10,45,210,30,"") 
      TextGadget(2,10,80,210,30,"Timer 1 and 2 Started",#PB_Text_Center) 

;/ Starting Timers 
SetTimer_(WindowID(0), 1, 150, @Timer1())   ; Timer #1 each 150 ms 
SetTimer_(WindowID(0), 2, 1000, @Timer2())  ; Timer #2 each 1 second 
SetTimer_(WindowID(0), 3, 10000, @Timer3()) ; Timer #3 each 10 seconds 
  
Repeat 
   Event = WaitWindowEvent() 

Until Event=#PB_Event_CloseWindow
Cheers,
Baldrick

Posted: Sun Jan 28, 2007 9:53 am
by Character
First I want to thank the forum again for all the interesting coding alternatives. These have all been very helpful.

But after all these months just 1 question keeps popping up in my mind:
what is the exact technical reason for threads being so unreliable. With other words: why does it behave so differently on various hardware configurations. It looks like some code is sometimes skipped and sometimes not. On some machines it goes never wrong, on some always.
(it is not CPU speed as far as I know)
I know there are many ways to bypass this problem but its just something that is teasing my brains. If anyone knows the exact technical cause and is able to explain it to a guy like me, it would be fantastic.

Update

Posted: Sun Dec 14, 2008 5:01 pm
by Character
Update:
It's a simple cooking timer written a while ago for own use. (see info above)
I thought sharing this simple PB-program cannot hurt anybody.
I have added an info screen and a couple of settings.
(thanks to Thorsten, author of EasySetup)

Freeware of course. :)
If anybody wants an Englisch version, please let me know.

Download exe.file here


Image