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
TThreadobject. -
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
CreateSuspendedis set toTrue, the thread won't start immediately. - If
CreateSuspendedis set toFalse, the thread will start running right after it's created. - If you create the thread with
CreateSuspended := True, you need to call theStartmethod 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
Terminatemethod simply changes theTerminatedproperty 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
FreeOnTerminateisTrue, the thread object is automatically freed when theExecutemethod finishes. This is useful for avoiding memory leaks and simplifying memory management in certain scenarios. - If
FreeOnTerminateisFalse, 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
OnTerminateevent handler should be set before settingFreeOnTerminatetoTrue - The properties can still be read and set in the
OnTerminateevent 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;¶
WaitForblocks 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
FreeOnTerminateandWaitFor:- You can safely use
WaitForwith a thread that hasFreeOnTerminateset toTrueif you ensureWaitForis called before the thread completes and frees itself.
- You can safely use
-
For multiple threads, it's safer to avoid combining
FreeOnTerminateset toTruewithWaitFor. Instead, manage the thread lifecycle manually:- Set
FreeOnTerminatetoFalse. - Call
WaitForon each thread to ensure it completes. - Manually
Freethe 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
Synchronizeto 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.