Multithreading, the Introduction
In Windows, and I believe also in any other modern multitasking operating systems, codes inside a program is nothing but a file unless they got loaded into memory. A loaded program in Windows is called a process. A process is a bit similar with multitasking operating system. Because it can execute several chains of codes at the same time. We call each chain of codes as thread. Codes are executed in the context of thread.
A process must have at least one thread, and a process may have many threads. Codes inside the same thread is blocking to each other. Meaning that if A and B are codes inside the same thread, and B's place is after A, then B must wait until A finished execution (returned) before it can start. It is different with codes of different thread. They are independent to each other. For example, if A and B are codes located in different thread, A and B might be executing in the same time.
The possibility to execute codes at the same time in the same process usually used to avoid the blocking of the main thread to wait for long time operations (such as I/O operation like reading disks). Operations that will take relatively long time could be assigned to different thread to make the operation not blocking the original thread execution. The result of the operation can be synchronized with the original thread either by interrupt or polling approach.
In Delphi GUI application, the GUI is maintained by the main thread. So if your code is blocking the main thread, your GUI will also not be able to be refreshed, which will make your application looks like hung. If you ever faced this kind of problem, you should consider to relocate the blocking codes to a separate thread. Properly done, it will banish your "hung" problem.
Of course multithreading requires proper synchronization. Because it opens the possibility of multiple operation accessing the same resource at the same time. When this is happening we want the state of the resource to be consistent.
Delphi has a nice framework for multithreading. There is a class named TThread which nicely wraps the APIs to create new thread and execute codes in it that makes it easy to work in separate thread.
In our demo project we have these missions:
- When told to, play beeping sound continuously.
- Gui must not look hung.
- When told to, the beeping sound must stop immediately.
- Create a new application. Save it under any name you like. I left mine as is, i.e. Project1.dpr.
- The application should already have a form named Form1
- Drop a TButton to Form1's top area. Name it btnStartBeepMultithread. Set its Caption to Start Beeping (Multithread). Adjust its width accordingly.
- Drop another TButton just below btnStartBeepMultithread. Name this button btnStartBeepNoThread. Give it caption of: Start Beeping (No multithread). Adjust its size accordingly.
- Drop another TButton below btnStartBeepNoThread. Name this button btnStopBeep. Give it caption of: Stop Beeping. Adjust its size accordingly.
Adjust the form and the buttons so you get something like the following:
Creating Beeper Thread
In the demo we want to place codes that sound the beeps inside separate thread, to get continous beeps without interfering with code execution in the main thread. Therefore we create a descendant of TThread, then override its Execute method and place the beeping codes inside it.
Let's name the new thread class as TBeeper. So declare it somewhere in the interface section of our Form1's unit. Declare it like:
type TBeeper=class(TThread) protected // the main body of the thread procedure Execute; override; end;
And put the beeping code in the Execute method implementation like this:
procedure TBeeper.Execute; begin // execute codes inside the following block until the thread is terminated while not Terminated do begin // play beep sound Beep; // yield the processor to other process/thread Sleep(0); // check the flag if uStopBeeping then Exit; end; end;
Note the uStopBeeping variable? We use it for flag to determine whether the beeping is still "playing" or has been stopped. It was a simple boolean variable local to the unit. So declare it somewhere in the implementation section of the unit.
Boolean variable of a unit usually initialized to false upon starting the program. In our case we want uStopBeeping starts with value of True since initially we don't sound the beep. We need to do that ini initialization of the unit. Place the following codes in the bottom area of the unit, just before the final end (end with a period).
initialization uStopBeeping := True;
To make it easier to use the continous beeping "feature", we provide two procedures to access the "feature". Declare the following procedures in interface section of the unit.
procedure StartBeeping; procedure StopBeeping;
And their implementation:
procedure StartBeeping; begin if not uStopBeeping then Exit; uStopBeeping := False; with TBeeper.Create(False) do // Tell the TBeeper instance to automatically destroy itself once it's been terminated FreeOnTerminate := True; end; procedure StopBeeping; begin uStopBeeping := True; end;
Coding the GUI
- Double click on btnStartBeepMultithread button to generate its onclick event handler skeleton code. And call StartBeeping from there.
procedure TForm1.btnStartBeepMultithreadClick(Sender: TObject); begin StartBeeping; end;
- Double click on btnStartBeepNoThread button to generate its onclick event handler skeleton code, and use the following code for it.
procedure TForm1.btnStartBeepNoThreadClick(Sender: TObject); begin uStopBeeping := False; // execute codes inside the following block until the thread is terminated while not uStopBeeping do begin // play beep sound Beep; // process pending messages might be in message queue Application.ProcessMessages; end; end;
- Double click on btnStopBeep button to generate its onclick event handler skeleton code, and call StopBeeping from there.
procedure TForm1.btnStopBeepClick(Sender: TObject); begin StopBeeping; end;
Run the Demo Project
Now the coding is done, time to test our demo program. Press F9 key to run our demo project, then you can experiment the beeping using multithreading and not.
Initially you can not notice the difference between beeping using multithreading and without one. That's because we were calling Application.ProcessMessages in beeping loop when we were not using multithreading. The calls executes GUI messages before re-sounding the beep. This made our GUI did not look hung. However if you were sounding the beep and then move the form around, the beep will cease to sound. This "bug" does not happen if you use beeping with multithreading.
Full source code of the demo project is attached.