Multi-threading with TThread
Part I¶
TThread
Class¶
Creating a multi-threaded application is easier using the TThread
class. This class lets you add another thread (in addition to the main thread) easily. Usually, you only need to override two methods: the constructor Create()
and the Execute
method.
By using TThread
, you can create and manage multiple threads in your application, making it more efficient and responsive.
Step-by-Step Guide¶
-
Declare a descendant of the
TThread
object. -
Override the
constructor Create();
- This is where you set up the thread.
- Initialise variables or properties you need.
- The constructor has a parameter called
CreateSuspended
:- If
CreateSuspended
is set toTrue
, the thread won't start immediately. - If
CreateSuspended
is set toFalse
, the thread will start running right after it's created. - If you create the thread with
CreateSuspended := True
, you need to call theStart
method later to run it.
- If
-
Override
Execute;
- This is where you write the code that will run in the thread.
- You can add a loop here to perform repeated actions.
Important features of TThread¶
property ProcessorCount: LongWord;
¶
- Returns the number of cores in the system.
procedure Terminate;
¶
- The
Terminate
method simply changes theTerminated
property toTrue
. - It does not in any way attempt to terminate the thread in any other way, this just signals the thread that it should stop executing at the earliest possible moment.
Important
When the thread contains a loop (which is common), the loop should end when Terminated
becomes True
(by default, it is False
). During each iteration, check the value of Terminated
, and if it is True
, exit the loop promptly after any required cleanup.
property FreeOnTerminate: Boolean;
¶
- If
FreeOnTerminate
isTrue
, the thread object is automatically freed when theExecute
method finishes. This is useful for avoiding memory leaks and simplifying memory management in certain scenarios. - If
FreeOnTerminate
isFalse
, you need to free the thread object manually. - Use the OnTerminate property to get a notification of when the thread has terminated and will be freed.
Tip
When setting FreeOnTerminate
property to True
, in general you may not read or write any property of the TThread
instance from a different thread, because there is no guarantee that the thread instance still exists in memory.
This implies 2 things:
- The
OnTerminate
event handler should be set before settingFreeOnTerminate
toTrue
- The properties can still be read and set in the
OnTerminate
event handler, as the thread instance is then still guaranteed to exist.
Tip
If FreeOnTerminate
is set to False
, to stop and delete a running thread from another thread, the following sample code can be used:
Source: https://www.freepascal.org/daily/doc/rtl/classes/tthread.freeonterminate.html
function WaitFor;
¶
WaitFor
blocks the calling thread until the thread it is called on has finished executing. This is useful for synchronizing the completion of a thread before proceeding with further operations.
Warning
The potential conflict arises because if a thread is set to FreeOnTerminate
, it will free itself immediately upon completion. If WaitFor
is called after the thread has terminated, it will attempt to access a freed (and thus invalid) thread object, leading to undefined behavior or program crashes.
Safe Usage Scenarios
-
Single Thread with
FreeOnTerminate
andWaitFor
:- You can safely use
WaitFor
with a thread that hasFreeOnTerminate
set toTrue
if you ensureWaitFor
is called before the thread completes and frees itself.
- You can safely use
-
For multiple threads, it's safer to avoid combining
FreeOnTerminate
set toTrue
withWaitFor
. Instead, manage the thread lifecycle manually:- Set
FreeOnTerminate
toFalse
. - Call
WaitFor
on each thread to ensure it completes. - Manually
Free
the thread objects.
- Set
Contribution
Gustavo 'Gus' Carreno 🇵🇹 🇬🇧 — 2024-05-27 at 15:53
You should never use the Terminate
property of a TThread
outside the thread itself.
You should always use WaitFor
[to wait for the thread to terminate]!!
Usually, you use Terminated
extensively in the Execute
method.
You kinda have to check it religiously inside Execute
, especially if you have a long running and/or blocking thread.
But, if I'm not mistaken, the Terminated
property is privacy level protected. Hence, you should not use it outside Execute
.
To terminate a thread you call Terminate
. Then if you need to make sure it's done and has cleaned up, you use WaitFor
.
procedure Synchronize();
¶
- Threads should not directly update visible components (like UI elements), so you must use
Synchronize
to safely update UI elements from the thread. Synchronize
...- pauses the thread,
- runs a method (like updating a label) in the main thread,
- and then resumes the thread.
Example: Run a task on a thread with a variable¶
- Create a class, for example
TMyThread
, based onTThread
. Line 14-25. - Create a constructor. Line 27-40.
- call constructor of
TThread
, - set free on terminate and
- Start thread.
- call constructor of
- Override
Execute
. Line 42-52.- This procedure contains your task to perform.
- Create a thread in the main block. Line 67.
- Wait for the thread to finish and return to the main thread. Line 71.
- Free the thread manually. Line 74.
Example: Run same tasks on multiple threads¶
Contribution
2024-02-08 - paweld 🇵🇱 caught a memory leak in the original code and fixed it.
Thank you!
- Create a class, for example
TTaskThread
, based onTThread
. Line 40-47. - Override
Execute
. Line 50-57.- This procedure contains your task to perform.
- Create a constructor. Line 60-68.
- call constructor of
TThread
, - set free on terminate and
- Start thread.
- call constructor of
- Create all threads in the main block. Line 56, 57.
- Wait for all threads to finish and return to the caller or main thread. Line 88, 89.
- Free threads manually. Line 92, 93.