My goal in two previous weather related projects was to include oral
weather reports and forecasts in my home automation system. It is not too
hard to do. Once the text is available, a two-step operation is all that is
required to hear it. First a speech synthesis engine produces a sound file
and, secondly, a utility plays the sound file. I use wav
file and
aplay
from ALSA as a player. Both these programs are available
on the Raspberry Pi Rasbian, the Orange Pi Zero
Armbian and on my Ubuntu
17.10 desktop machine.
Previously, I used a straightforward naive approach, which consisted of two consecutive calls to the operating system returning to the program only after both utilities had terminated. This is what it looks like in Python and Free Pascal.
This is not desirable because a forecast can take up to two or three minutes during which time the user interface is frozen. Maybe two or three days of forecasts is enough, with no need to go on and on for another seven or eight days. I wanted the following.
- Focus must return to the calling program as soon as the sound. file begins to play.
- The calling program must be made aware when the utility has finished playing the sound file.
- The calling program must be able to terminate the playing of the
sound file any time after it started to play. This last requirement is
actually a bit more complicated than it sounds. I want
- the ability to stop playing the sound file whenever the user signals that need, and
- the ability to optionally stop playing the sound file or continue to play the sound file when the calling program is itself terminated.
Running a process in a thread seemed like the obvious solution. Trouble is that I didn't know much about threads. In many Pascal/Delphi programs I managed to avoid them by using the application idle loop to do asynchronous tasks. Most times that was just to update the state of a program's GUI. Often I eliminated that need by explicitly updating the display only when necessary.
As usual when tackling a new problem, I tried to get information on the subject. And as usual, it was rather incomprehensible at first and I was having some difficulty in getting this to work as I wanted because of my shaky understanding of the mechanics of threads. Again, I have to thank the community for being so willing to share knowledge and experience. The Thread (computing) article at Wikipedia and the Threads course notes by John T. Bell at the University of Illinois, Chicago were helpful.
Threads are tasks that are executed in "parallel". They appear to be working simultaneously, but on a single core computer that is not possible. Instead, all the threads are being performed a few instructions at a time. With modern multi-core computers, threads can truly execute in parallel if assigned to different cores. All threads could be given an equal slice of core time and be executed one after the other in round-robin fashion. Or certain threads could be given a higher priority meaning they get a larger slice of time. Back before OS X, the Mac OS required threads to cooperate meaning that each had to yield control of the core after a reasonable amount of time used so that the other threads could be given a slice of time also. Just think about it. If you made a mistake in a thread, you could easily bring down the whole system. As far as I know, main stream OS now use preemptive scheduling so that other threads will continue to be executed even if one has gone off on a wild goose chase. The allocation of time, scheduling and the switching from thread to thread is all done by the operating system.
Synchronising threads can be a problem. What if thread A needs results calculated by thread B? A does not know when B will be finished, so it may have to wait for it to be done. And two threads could be trying to use the same resource at the "same time." And a resource could be something as innocent looking as a string or other variable. Will the variable contain what A wanted to write to it or what B wanted to write to it or some mishmash?
The Free Pascal run time library TThread
class is a wrapper
around the OS threads so, thankfully, we don't need to know much about the
bare metal aspect of these little beasts. The basic idea is to create
a descendant class of TThread
overriding a few methods. Usually
the constructor is used to set up the particulars of the descendant
threads. And then the Execute
procedure is overridden because
that is where the thread will be doing its task.
I did run into synchronisation problems. Remember that an application is itself a thread so that when a TThread is created, it has to be careful if it interacts with any user interface object or anything that in turn accesses application resources. But my biggest problem was with memory leaks when interrupting the sound player. I found the detailed help I needed in the Lazarus forum. And I have to thank search engines because I have yet to learn how to use the forum search engine efficiently. I strongly recommend the four pages of entries on the topic Playing with thread, memory leaks.
It started to sink in by the time I got to the answer from Blestan. Since I was setting the thread
freeOnTerminate
property to True
, neither
waitfor
nor terminate
were to be used. Furthermore,
all the cleanup had to be performed in the execute
method
after the while
loop. So here is the small unit I wrote based on
those recommendations.
The work is done by a TThread
descendant,
TPlayThread
. Just about everything happens in the overridden
execute
method. It starts by creating a process which launches
the aplay
utility and returns immediately (notice that the
option poWaitOnExit
is not included here). The following
while
loop will run as long as aplay
runs. On
completing the loop the process is freed and the OnPlayDone
event is fired. Two flags are used to interrupt the while
loop.
The StopPlay
flag which is set by calling the
StopPlaying
function. The AppClosing
flag is set
during unit finalization
and will only be true when the
application is, surprise, surprise, closing. When execute
is
completed, the thread is automatically freed. No matter what, both the
process object and the thread will be freed, so there will not be a memory
leak.
When the while
loop is interrupted before aplay
has finished running, the latter will in turn be interrupted if the
StopPlay
flag had been set using the StopPlaying
function. That is how it will be possible for the user to interrupt
aplay
. Remember the application thread is running in parallel
with our thread that is doing basically nothing. So the application user
interface is not frozen. If the interruption of the while
loop
was caused by the termination of the application, then aplay
will be stopped only if the FTerminateProcess
field is true.
Otherwise, it can continue playing its merry tune even as the application
winds down.
The need to signal the coming end of the application in order to free the
thread was outlined in the forum discussion by Fred Vs and BlueIcaro. However, contrary to Blestan's admonition,
they both invoke the thread terminate
method while the
freeOnTerminate
property is set to true. I did not like using
a global boolean variable nor using the GUI event to look after freeing a
thread. It seemed like something I would easily forget to do in a future
use of the unit. As a first solution, I used the
terminated
field of the application
object as a flag in the while
loop
instead of AppClosing
. That too was not satisfactory, because
playunit
is based on threads and processes which could be useful
in console applications without forms and the application
object.
I was happy to discover that a simple flag set in the finalization
part of the unit was just as good.
It is mandatory to test for the application termination (i.e. checking if
AppClosing
or or application.terminated
is set to
true
) before triggering the OnPlayDone
event. It
that test is not performed, there is every chance that a hard to trace
exception will occur because in all likely hood the event handler will have
been destroyed by then time the event is fired. And note Done
is called indirectly with the syncrhonize
procedure. That
takes care of any synchronisation problem that could be caused by the
OnPlayDone
handler.
The unit does not expose much. The TPlayThread
and
TProcess
objects that are created are entirely private.
Even the StopPlay
flag is set indirectly with the
StopPlaying
procedure. The Play
procedure is
used to launch aplay
in a thread. Its arguments are
the name of the file to play, the address of the OnPlayDone
event
handler and a boolean flag to automatically stop or not aplay
when
the application is shut down. These last two parameters are optional and
by default, aplay
will be automatically shut down.
The small test_thread
project can be used to see how playunit
is used.
Debug
(Projet/Project Options/Compiler Options/Build modes Debug).
Then start a terminal and start the application. Initially, the Stop button is disabled. When the Play
button is pressed it is disabled, the Stop is
enabled and the Play
procedure is invoked.
When the wav
file is finished playing because
aplay
arrived at the end or because the Stop
button was pressed, the OnDoneEvent
handler is fired and
it restores the buttons enable property to their initial values. Pressing
the Quit button will interrupt aplay
depending on the state of the checkbox.
The program has a different behaviour if run within the Lazarus IDE.
No matter the setting of the checkbox aplay
will be shut down
by the IDE when the Quit button is pressed.
If the test application is terminated in the first few seconds after
starting aplay
, the latter will be terminated along with the
application no matter the setting of the checkbox. Don't know why that is.
Oh well, can't win them all.