I know that I must call Synchronize to update the vcl from a thread that did not create the controls or send a message to the window.
I have often heard the word not thread safe but I can't find an actual explanation about what is happening.
I know the application might crash with an access violation, but again I don't know why?
Please shed a light on this topic.
One of the biggest causes of the thread-unsafety in the VCL UI controls is the TWinControl.Handle
property getter. It is not just a simple read-only accessor of the control's HWND
. It also creates the HWND
if it does not exist yet. If a worker thread reads the Handle
property when no HWND
exists yet, it creates a new HWND
within the worker thread context, which is bad because HWND
s are tied to the creating thread context, which would render the owning control pretty much inoperable at best since Windows messages for the control would not go through the main message loop anymore. But worse, if the main thread reads the same Handle
property at the same time the worker thread does (for instance, if the main thread is dynamically recreating the Handle
for any number of reasons), there is a race condition between which thread context creates the HWND
that gets assigned as the new Handle
, as well as a potential handle leak potential if both threads end up creating new HWND
s but only one can be kept and the other gets leaked.
Another offender to thread-unsafety is the VCL's MakeObjectInstance()
function, which the VCL uses internally for assigning the TWinControl.WndProc()
non-static class method as the message procedure of the TWinControl.Handle
window, as well as assigning anyTWndMethod
-typed object method as the message procedure of the HWND
created by the AllocateHWnd()
function (used by TTimer
for example). MakeObjectInstance()
does quite a bit of memory allocating/caching and twiddling of that memory content which are not protected from concurrent access by multiple threads.
If you can ensure a control's Handle
is allocated ahead of time, and if you can ensure the main thread never recreates that Handle
while the worker thread is running, then it is possible to safely send messages to that control from the worker thread without using Synchronize()
. But it is not advisable, there are just too many factors that the worker thread would have to take into account. That is why it is best that all UI access be done in the main thread only. That is how the VCL UI system is meant to be used.
About GDI thread safety in Windows, see this reference article.
It clearly states that you can access safely handles from multiple threads, but that it should not be made at the same time. You need to protect access to GDI handles, e.g. using critical sections.
Remember that GDI handles, like most Windows handles, are pointers of internal structures mapped to an integer
(NativeUInt
under newer Windows, for 64 bit compatibility). Like always in multi-thread computing, accessing the same content concurrently can be source of problems, which are very difficult to identify and fix.
The UI part of the VCL itself was never meant to be thread-safe, from the beginning, since it was relying on the non-thread-safe Windows API. For instance, if you release a GDI object in a thread, which is still needed in another thread, you'll face potential GPF.
Embarcadero (at this time) could have made the VCL thread-safe, serializing all UI access via critical sections, but it may have added complexity, and decreased overall performance. Note that even Microsoft .Net platform (in both WinForms and WPF) also requires a dedicated thread for UI access, AFAIK.
So, to refresh UI from multiple threads, you have several patterns:
Synchronize
calls from the thread; WM_USER
) from the background threads to notify the UI thread that a refresh is needed; From my point of view, I prefer option 2 for most UIs, and an additional option 3 (which can be mixed with option 2) for remote client-server access. Therefore, you do not have to want from the server side to trigger some update event to the UI. In a HTTP/AJAX RESTful world, this does definitively make sense. Option 1 is somewhat slow, IMHO. In all cases, options 2 and 3 expect a clear n-Tier layered architecture, in which logic and UI are not mixed: but this is a good pattern to follow anyway, for any serious development.
Windows controls with handles are not thread-safe (i.e. they cannot be accessed safely by two different threads at the same time), and Delphi wraps the Windows controls to give you the VCL controls. Since the controls ARE accessed by the main GUI thread, you need to leave them alone if you are executing another thread.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With