Running a Process in a Thread (Free Pascal)
February 24, 2018
<-The Yahoo! Weather API with Free Pascal The cthreads Unit in Linux (Free Pascal)->

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 pico tts (test-to-speech) to create a 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.["pico2wave", "-l", lang, "-w", wavfile, words])["aplay", wavfile])
RunCommand('pico2wave', ['-l', lang, '-w', wavfile, words], output, [poWaitOnExit, poNoConsole]); RunCommand('aplay', [wavfile], output, [poWaitOnExit, poNoConsole]);

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.

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.

unit playunit; {$mode objfpc}{$H+} interface uses Classes, SysUtils; procedure Play(filename: string; PlayDone: TNotifyEvent = nil; TerminateProcess: boolean = true); procedure StopPlaying; implementation uses process; var StopPlay: boolean = false; AppClosing: boolean = false; type TPlayThread = class(TThread) private procedure Done; protected procedure Execute; override; public FOnPlayDone: TNotifyEvent; FFilename: string; FTerminateProcess: boolean; constructor Create(const filename: string; PlayDone: TNotifyEvent = nil; TerminateProcess: boolean = true); end; { TPlayThread } constructor TPlayThread.Create(const filename: string; PlayDone: TNotifyEvent; TerminateProcess: boolean); begin FOnPlayDone := PlayDone; FTerminateProcess := TerminateProcess; FFilename := filename; inherited create(false); FreeOnTerminate := true; end; procedure TPlayThread.Done; begin if assigned(FOnPlayDone) then FOnPlayDone(self); end; procedure TPlayThread.Execute; var PlayProc: TProcess; begin PlayProc := TProcess.create(nil); try PlayProc.executable := 'aplay'; PlayProc.parameters.add(FFilename); PlayProc.Options := [poNoConsole]; PlayProc.execute; while PlayProc.Running do begin if StopPlay or AppClosing then begin if StopPlay or FTerminateProcess then PlayProc.terminate(1); exit; end else sleep(1); end; finally; if assigned(FOnPlayDone) and not AppClosing then synchronize(@Done); end; end; procedure Play(filename: string; PlayDone: TNotifyEvent; TerminateProcess: boolean); begin StopPlay := false; TPlayThread.create(filename, PlayDone, TerminateProcess); end; procedure StopPlaying; begin StopPlay := true; end; finalization AppClosing := true; end.

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.

Compile it and making sure that the build mode is 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.
michel@hp:~$ cd Development/fpl-projects/test_thread or whatever directory the project is in michel@hp:~/Development/fpl-projects/test_thread$ ./test_thread Heap dump by heaptrc unit 1314 memory blocks allocated : 129238/131456 1314 memory blocks freed : 129238/131456 0 unfreed memory blocks : 0 True heap size : 131072 True free heap : 131072

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.

<-The Yahoo! Weather API with Free Pascal The cthreads Unit in Linux (Free Pascal)->