Recent

Author Topic: [SOLVED] Accessing a variable inside and outside a timer's event.  (Read 1079 times)

EganSolo

  • Sr. Member
  • ****
  • Posts: 290
Context: I have a program that needs to play some sound while displaying text at a specified interval, meaning:
  Play sound
  Display first text
  Wait some seconds
  Display more text
  Wait some more
  Display more text

The music needs to continue playing while the wait it taking place. I don't think I could use sleep because sleep would stop the program as a whole and would interrupt the music ...

My current approach is to play the sound in the main program and rely on a timer to implement the wait as illustrated below:

Code: Pascal  [Select][+][-]
  1. Type
  2.   TMyClass = class
  3.   private
  4.      fDone : Boolean;
  5.      fTimer : TTimer;
  6.      Procedure OnTimer(Sender: TObject);
  7.      Procedure WaitForTwoSeconds;
  8.   end;
  9.  

Assume the constructor has initialized the timer and assigned OnTimer as an event handler to OnTimer. The body of this method sets fDone to true as seen below:
Code: Pascal  [Select][+][-]
  1. Procedure MyClass.Timer1StopTimer(Sender : TObject);
  2. begin
  3.     fDone := true;
  4. end;
  5.  

Lastly, the method WaitForTwoSeconds will assign a string to a variable, loop waiting for fDone to be true and then assign a second string to the local variable. The example is silly, I know, but it illustrates the problem I'm trying to solve:
Code: Pascal  [Select][+][-]
  1. Procedure MyClass.WaitTwoseconds;
  2. var S: String;
  3. begin
  4.   fDone := false;
  5.   fTimer.Interval := 2000;
  6.   S := 'This is the first sentence';
  7.   fTimer.Start;
  8.   //Now wait on the timer to be done.
  9.   While Not fDone do Application.ProcessMessages;
  10.   S := 'This is the second sentence';
  11. end;
  12.  

My question: Since fDone is not protected by semaphores, there will be a potential race condition between the thread within the timer and the main thread. Does it matter? Do I need to worry about it, and if so what would be the best way to handle this? Do I need to have recourse to actual threads?

« Last Edit: May 06, 2023, 11:12:38 pm by EganSolo »

TRon

  • Hero Member
  • *****
  • Posts: 3791
Re: Accessing a variable inside and outside a timer's event.
« Reply #1 on: May 06, 2023, 08:41:10 am »
My question: Since fDone is not protected by semaphores, there will be a potential race condition between the thread within the timer and the main thread.
1. Does it matter?
2. Do I need to worry about it, and if so what would be the best way to handle this?
3. Do I need to have recourse to actual threads?
1. No (*)
2. No (*)
3. No (*)

(*) in the way you are showing us how you want to do things.

Though my take on it would be to play the music inside a thread so that you do not have to worry about that.
I do not have to remember anything anymore thanks to total-recall.

EganSolo

  • Sr. Member
  • ****
  • Posts: 290
Re: Accessing a variable inside and outside a timer's event.
« Reply #2 on: May 06, 2023, 10:12:03 am »
@TRON: thank you very much for your quick reply! That sure helps.
For my edification, could you please expand your answer slightly? Isn't the timer running in its own thread and the main program in a separate thread? If so, won't we have two threads attempting to access the same variable, one to read it and the other to update it? Are there safeguards that the compiler injects to protect against such race conditions? What am I missing?

Your point about running the music in its own thread is well-taken and it is definitely a good option.

alpine

  • Hero Member
  • *****
  • Posts: 1319
Re: Accessing a variable inside and outside a timer's event.
« Reply #3 on: May 06, 2023, 01:05:10 pm »
@EganSolo
No need to worry about multi threading, TTimer works via messages into the message queue, so it's handlers are executed into the main thread.

You can achieve that by something like:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Label1: TLabel;
  17.     Timer1: TTimer;
  18.     procedure Button1Click(Sender: TObject);
  19.     procedure Timer1Timer(Sender: TObject);
  20.   private
  21.     FState: Integer;
  22.     FElapsed: Cardinal;
  23.   public
  24.  
  25.   end;
  26.  
  27. var
  28.   Form1: TForm1;
  29.  
  30. implementation
  31.  
  32. uses
  33.   MMSystem;
  34.  
  35. {$R *.lfm}
  36.  
  37. { TForm1 }
  38.  
  39. procedure TForm1.Timer1Timer(Sender: TObject);
  40. begin
  41.   case FState of
  42.     // 0: Idle
  43.  
  44.     1: // Step 1: Play sound
  45.       PlaySound('C:\SOUNDS\BELLS.WAV', 0, SND_ASYNC);
  46.  
  47.     2: // Step 2: Display first text
  48.       begin
  49.         Label1.Caption := 'This is the first sentence';
  50.         FElapsed := 0;
  51.       end;
  52.  
  53.     3: // Step 3: Wait some seconds
  54.       begin
  55.         FElapsed := FElapsed + Timer1.Interval;
  56.         if FElapsed < 2000 then
  57.           Exit;
  58.       end;
  59.  
  60.     4: // Step 4: Display more text
  61.       begin
  62.         Label1.Caption := 'This is the second sentence';
  63.         FElapsed := 0;
  64.       end;
  65.  
  66.     5: // Step 5: Wait some more seconds
  67.       begin
  68.         FElapsed := FElapsed + Timer1.Interval;
  69.         if FElapsed < 2000 then
  70.           Exit;
  71.       end;
  72.  
  73.     6: // Step 6: Display more text
  74.       begin
  75.         Label1.Caption := 'This is the third sentence';
  76.         FElapsed := 0;
  77.       end;
  78.  
  79.     {...}
  80.  
  81.     otherwise
  82.       FState := 0; // Back to idle
  83.       Exit;
  84.   end;
  85.   Inc(FState);
  86. end;
  87.  
  88. procedure TForm1.Button1Click(Sender: TObject);
  89. begin
  90.   FState := 1; // Start
  91. end;
  92.  
  93. end.
  94.  

You must assign Timer1.Interval to something like 100 (i.e. way smaller than a second).
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

440bx

  • Hero Member
  • *****
  • Posts: 4900
Re: Accessing a variable inside and outside a timer's event.
« Reply #4 on: May 06, 2023, 01:57:32 pm »
Unless the sound you intend to play is very short, like the typical duration of a beep, you should probably have a separate thread play it.

Also, even if you use a separate thread, it's unlikely you'll need to synchronize access to the time variable since the type of access will be different for both threads and, being "off" by one tick is inconsequential and rather unlikely to be humanly perceptible.

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

alpine

  • Hero Member
  • *****
  • Posts: 1319
Re: Accessing a variable inside and outside a timer's event.
« Reply #5 on: May 06, 2023, 02:03:13 pm »
Unless the sound you intend to play is very short, like the typical duration of a beep, you should probably have a separate thread play it.

Code: Pascal  [Select][+][-]
  1. PlaySound('C:\SOUNDS\BELLS.WAV', 0, SND_ASYNC);

Will start playing and return immediately (SND_ASYNC).
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

EganSolo

  • Sr. Member
  • ****
  • Posts: 290
Re: Accessing a variable inside and outside a timer's event.
« Reply #6 on: May 06, 2023, 11:12:24 pm »
@Alpine: Thank you for your answer! knowing that TTimer is sending messages to the main message loop resolved my issue. Also, I really liked how you use TTimer to create an actual workflow. It's a great technique and I'm going to reuse it.

Much appreciated!

alpine

  • Hero Member
  • *****
  • Posts: 1319
Re: Accessing a variable inside and outside a timer's event.
« Reply #7 on: May 07, 2023, 12:37:35 am »
@Alpine: Thank you for your answer! knowing that TTimer is sending messages to the main message loop resolved my issue. Also, I really liked how you use TTimer to create an actual workflow. It's a great technique and I'm going to reuse it.

Much appreciated!
You're welcome!

This is just the simplest way to implement a state machine that is suitable for such a (simple) case.
IMHO it is worth exploring the asynchronous execution library in that thread for the more complex ones.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

 

TinyPortal © 2005-2018