I was wondering if it's safe to read values from a VCL control outside the main thread.
Let's say I read line after line (or it's caption property) from a TMemo/TEdit control from a thread (without TThread.Synchronize), but also made sure the TMemo/TEdit control has been disabled and/or it's read only flag has been enabled.
Would it be safe?
No this is not safe. The content from memo and edit controls is read by calling the window procedure (using the Perform method), passing WM_GETTEXTLENGTH and WM_GETTEXT. Such code is required by Win32 to be executed on the thread that created the window, in this case the main thread. So when you read the Text property from a worker thread, you break that rule, because you call Perform on the worker thread, and then execute the window procedure on the wrong thread.
You might instead think that you could use SendMessage passing WM_GETTEXTLENGTH/WM_GETTEXT to arrange that the window procedure was executed on the main thread. But that is subject to a nasty race condition. You need a window handle to use SendMessage, but accessing the Handle property on the worker thread is not threadsafe. That must be done on the main thread, otherwise VCL window re-creation can lead to the window being created by your worker thread. And GUI windows must be created by the main thread.
Related
Everywhere is noticed that VCL is not thread-safe and we must synchronize access to it. So it's VCL faults that is not thread-safe.
How VCL itself can be thread-safe?
What, precisely, does "thread-safe" mean to you? What about someone else? Every time I see this brought up, it ends up boiling down to this: "I want VCL to be thread-safe so I don't have to think about threading and synchronization issues. I want to write my code as if it is still single-threaded."
No matter how much work went into making VCL so-called "thread-safe", there will always be situations where you can get into trouble. How would you go about making it thread-safe? I don't say this to be combative, rather I merely want to demonstrate that it is not a simple problem with a simple, "works-in-all-cases" solution. To highlight this, let's look at some potential "solutions."
The simplest and most direct approach I see is each component has some kind of "lock", say a mutex or critical section. Every method on the component grabs the lock on entry and then releases the lock just prior to exit. Let's continue down this path with a thought experiment. Consider how Windows processes messages:
Main thread obtains a message from the message queue and then dispatches it to the appropriate WndProc. This message is then routed to the appropriate TWinControl component. Since the component has a "lock", as the message is routed to the appropriate message handler on the component, the lock is acquired. So far so good.
Now take the proverbial button-click message processing. The OnClick message handler is now called which will most likely be a method on the owning TForm. Since the TForm descendant is also a TWinControl component, the TForm's lock is now acquired while the OnClick handler is processed. Now the button component is locked and the TForm component is also locked.
Continuing on this line of thinking, suppose the OnClick handler now wants add an item to a listbox, listview, or some other visual list or grid component. Now suppose some other thread (not the main UI thread) is already in the midst of accessing this same component. Once a method is called on the list from the UI thread it will attempt to acquire the lock, which it cannot since the other thread is currently holding it. As long as the non-UI thread doesn't hold that lock for very long, the UI thread will only block for a brief period.
So far so good, right? Now suppose, that while the non-UI thread is holding the list control's lock, a notification event is called. Since, it will most likely be a method on the owning TForm, upon entry to the event handler, the code will attempt to acquire the lock for the TForm.
Do you see the problem? Remember the button OnClick handler? It already has the TForm lock in the UI thread! It is now blocked waiting for the lock on the list control, which the non-UI thread owns. This is a classic dead-lock. Thread A holds lock A and attempts to acquire lock B which is held by thread B. Thread B is at the same time attempting to acquire lock A.
Clearly, if every control/component has a lock that is automatically acquired and released for every method isn't a solution. What if we left the locking up to the user? Do you see how that also doesn't solve the problem either? How can you be certain that all the code you have (including any third-party components) properly locks/unlocks the controls/components? How does this keep the above scenario from happening?
What about a single shared lock for the whole of VCL? In this scenario, for each message that is processed, the lock is acquired while the message is processed regardless of what component the message is routed to. Again, how does this solve a similar scenario I described above? What if the user's code added other locks for synchronization with other non-UI threads? Even the simple act of blocking until a non-UI thread terminates can cause a dead lock if it is done while the UI thread holds the VCL lock.
What about non-UI components? Database, serial, network, containers, etc...? How should they be handled?
As excellently explained by the other answers, Windows already does a pretty decent job of properly segregating UI message processing to only the thread on which each HWND is created. In fact, learning precisely how Windows works in this regard will go a long way to understanding how you can write your code to work with Windows and VCL in a manner that avoids most of the pitfalls I highlighted above. The bottom line is writing multi-threaded code is difficult, requires a rather drastic mental shift, and lots of practice. Read as much as you can on multi-threading from as many sources as possible. Learn and understand as many coding examples of "thread-safe" code as you can, in any language.
Hopefully this was informative.
The VCL is not thread safe. It is a wrapper around Win32. Win32 is thread safe but has threading rules that give meaning to that statement. Most specifically a window has affinity to the thread that created it.
The design of the Windows message queue means that it is almost always preferable to have all your GUI windows created by the main thread. The VCL designers decided that it was reasonable only to support that mode of operation. And so all VCL code must be executed from the main thread.
There's nothing that can be done to change this. This is by design. If you wish to execute VCL code, it must be done on the main thread. Use TThread.Synchronize or TThread.Queue to arrange that.
There are a lot of reasons why the VCL (especially UI controls) is not thread safe.
Race conditions on message input, especially in code that directly calls TControl.Perform()/TObject.Dispatch() instead of using PostMessage()/SendMessage(). The former does not perform any synchronizing of the control's message handlers, but the latter does. So it is not safe to perform non-HWND based messages from outside of the main thread.
An HWND has thread affinity. It receives and processes messages only on, and can be destroyed only on, the thread context that creates it. A TWinControl can destroy and recreate its HWND at any time, even multiple times, during its lifetime. The TWinControl.Handle property getter creates a new HWND if none exists yet. So if the control is in the process of recreating its HWND when another thread reads from the Handle property, the control can end up with a new HWND that was created in the wrong thread context, making the control no longer responsive to the main message loop (and can potentially leak a second HWND as well). So it is not safe to read from the TWinControl.Handle property from outside of the main thread.
The VCL has a MakeObjectInstance() function that creates a dynamic proxy to allow a TWndMethod class method to be used as a Win32 WNDPROC window callback procedure. All TWinControl controls, and some utility classes like TTimer, use this function. Internally, it maintains a global linked list of proxies, and that list is not protected from concurrent access across threads. So it is not safe to create/destroy HWND-based VCL controls from outside of the main thread.
I'm sure there are other reasons, but these are the big ones.
Everywhere is noticed that VCL is not thread-safe and we must synchronize access to it. So it's VCL faults that is not thread-safe.
How VCL itself can be thread-safe?
What, precisely, does "thread-safe" mean to you? What about someone else? Every time I see this brought up, it ends up boiling down to this: "I want VCL to be thread-safe so I don't have to think about threading and synchronization issues. I want to write my code as if it is still single-threaded."
No matter how much work went into making VCL so-called "thread-safe", there will always be situations where you can get into trouble. How would you go about making it thread-safe? I don't say this to be combative, rather I merely want to demonstrate that it is not a simple problem with a simple, "works-in-all-cases" solution. To highlight this, let's look at some potential "solutions."
The simplest and most direct approach I see is each component has some kind of "lock", say a mutex or critical section. Every method on the component grabs the lock on entry and then releases the lock just prior to exit. Let's continue down this path with a thought experiment. Consider how Windows processes messages:
Main thread obtains a message from the message queue and then dispatches it to the appropriate WndProc. This message is then routed to the appropriate TWinControl component. Since the component has a "lock", as the message is routed to the appropriate message handler on the component, the lock is acquired. So far so good.
Now take the proverbial button-click message processing. The OnClick message handler is now called which will most likely be a method on the owning TForm. Since the TForm descendant is also a TWinControl component, the TForm's lock is now acquired while the OnClick handler is processed. Now the button component is locked and the TForm component is also locked.
Continuing on this line of thinking, suppose the OnClick handler now wants add an item to a listbox, listview, or some other visual list or grid component. Now suppose some other thread (not the main UI thread) is already in the midst of accessing this same component. Once a method is called on the list from the UI thread it will attempt to acquire the lock, which it cannot since the other thread is currently holding it. As long as the non-UI thread doesn't hold that lock for very long, the UI thread will only block for a brief period.
So far so good, right? Now suppose, that while the non-UI thread is holding the list control's lock, a notification event is called. Since, it will most likely be a method on the owning TForm, upon entry to the event handler, the code will attempt to acquire the lock for the TForm.
Do you see the problem? Remember the button OnClick handler? It already has the TForm lock in the UI thread! It is now blocked waiting for the lock on the list control, which the non-UI thread owns. This is a classic dead-lock. Thread A holds lock A and attempts to acquire lock B which is held by thread B. Thread B is at the same time attempting to acquire lock A.
Clearly, if every control/component has a lock that is automatically acquired and released for every method isn't a solution. What if we left the locking up to the user? Do you see how that also doesn't solve the problem either? How can you be certain that all the code you have (including any third-party components) properly locks/unlocks the controls/components? How does this keep the above scenario from happening?
What about a single shared lock for the whole of VCL? In this scenario, for each message that is processed, the lock is acquired while the message is processed regardless of what component the message is routed to. Again, how does this solve a similar scenario I described above? What if the user's code added other locks for synchronization with other non-UI threads? Even the simple act of blocking until a non-UI thread terminates can cause a dead lock if it is done while the UI thread holds the VCL lock.
What about non-UI components? Database, serial, network, containers, etc...? How should they be handled?
As excellently explained by the other answers, Windows already does a pretty decent job of properly segregating UI message processing to only the thread on which each HWND is created. In fact, learning precisely how Windows works in this regard will go a long way to understanding how you can write your code to work with Windows and VCL in a manner that avoids most of the pitfalls I highlighted above. The bottom line is writing multi-threaded code is difficult, requires a rather drastic mental shift, and lots of practice. Read as much as you can on multi-threading from as many sources as possible. Learn and understand as many coding examples of "thread-safe" code as you can, in any language.
Hopefully this was informative.
The VCL is not thread safe. It is a wrapper around Win32. Win32 is thread safe but has threading rules that give meaning to that statement. Most specifically a window has affinity to the thread that created it.
The design of the Windows message queue means that it is almost always preferable to have all your GUI windows created by the main thread. The VCL designers decided that it was reasonable only to support that mode of operation. And so all VCL code must be executed from the main thread.
There's nothing that can be done to change this. This is by design. If you wish to execute VCL code, it must be done on the main thread. Use TThread.Synchronize or TThread.Queue to arrange that.
There are a lot of reasons why the VCL (especially UI controls) is not thread safe.
Race conditions on message input, especially in code that directly calls TControl.Perform()/TObject.Dispatch() instead of using PostMessage()/SendMessage(). The former does not perform any synchronizing of the control's message handlers, but the latter does. So it is not safe to perform non-HWND based messages from outside of the main thread.
An HWND has thread affinity. It receives and processes messages only on, and can be destroyed only on, the thread context that creates it. A TWinControl can destroy and recreate its HWND at any time, even multiple times, during its lifetime. The TWinControl.Handle property getter creates a new HWND if none exists yet. So if the control is in the process of recreating its HWND when another thread reads from the Handle property, the control can end up with a new HWND that was created in the wrong thread context, making the control no longer responsive to the main message loop (and can potentially leak a second HWND as well). So it is not safe to read from the TWinControl.Handle property from outside of the main thread.
The VCL has a MakeObjectInstance() function that creates a dynamic proxy to allow a TWndMethod class method to be used as a Win32 WNDPROC window callback procedure. All TWinControl controls, and some utility classes like TTimer, use this function. Internally, it maintains a global linked list of proxies, and that list is not protected from concurrent access across threads. So it is not safe to create/destroy HWND-based VCL controls from outside of the main thread.
I'm sure there are other reasons, but these are the big ones.
why the application hangs when Executing Thread and that thread have ShowMessage or MessageDlg
but when using MessageBox everything is working normally.
all this happened if the application Appearance not the defualt one >> "Windows"
if the selected Appearance "Windows" it will never hangs even with the ShowMessage and MessageDlg
ShowMessage() and MessageDlg() are not thread-safe. They display VCL Forms, which must only be used in the context of the main UI thread.
Windows.MessageBox() is generally thread-safe, if you specify a nil owner window when calling it from a worker thread. It creates and displays its own dialog window, and runs its own modal message loop, within the context of the calling thread, so there are usually no threading issues. But there are some gotchas (see this article: MessageBoxes and worker threads).
TApplication.MessageBox() calls Windows.MessageBox() internally, but is not thread-safe because it does things invoking the RTL and MainForm that are not thread-safe, and thus should only be used in the context of the main UI thread as well.
In short, don't use VCL popup messages in worker threads - period. Use Windows.MessageBox(), or to be on the safe side, delegate you popup messages to the main UI thread.
I am working on a VCL (Delphi Win32) forms application platform with embedded scripting and would like to add debugging support. Scripting executes in the main VCL thread - scripts do direct UI manipulation and has some other legacy constraints keeping it in the UI thread.
The debugger UI needs to run in its own thread since the main UI thread will block on script breakpoints. It still needs to be in the same process for the thread-safe debugging component to work.
I tried to follow Blorgbeard's comment on https://stackoverflow.com/a/12505959/243144, but I am not sure if this is even possible with Delphi's VCL. (.NET creates a new ApplicationContext when passing a form to Application.Run) With the following Delphi, blocking of the main UI thread stops message processing on the second thread (and vice versa).
procedure TDebuggerThread.Execute;
begin
CoInitialize(nil);
FForm := TForm2.Create(nil);
FForm.Show;
Application.Run;
end;
Delphi forms are single-thread only. Any descendant of TControl, including TForm, must only be accessed from the main UI thread. Never call any TApplication method on anything but the main thread.
You're of course allowed to create other windows that are tied to different threads. You just can't use the VCL UI elements in those threads. Instead, you'll use CreateWindow to create the main window and any controls on it. You'll write a window procedure to handle any messages sent to those windows. You'll handle control-notification messages in the parent's window procedure rather than the child's.
One of the constraints of the VCL is that all interaction with GUI controls must be performed on the main thread. There's no way to circumvent that.
If you want to show debugger GUI in a separate thread, using VCL, your only option is to use an out-of-process solution. In other words, run your debugger in a different process, and use IPC to communicate between the two processes.
I have a Delphi7 program which implements a com server. Recently the time for program initialisation to complete has increased somewhat due to some new database queries. What I mean by program initlisation is the time it takes for the program to be able to process the incoming com calls in a meaningful way.
My question is how I can handle incoming com calls when the program is not properly initialised yet. Discarding the information contained in the call is not an option, the call needs to wait for the program to initialise.
Will a simple while loop using a boolean initialisation variable be sufficient?
I ask because I read that com calls are executed in the main VCL thread, which would suggest that a while loop would cause the program to hang (The initialisation takes place in the VCL thread as well).
Mutexes cannot be used to waitfor code executing within the same thread, right?
To put it another way, does a com call interupt execution of the (com server's) VCL thread until it is done or how does this concurrent execution of code within the same thread work?
Thanks for taking the time.
Move your database logic out of the main thread so it is not blocked anymore.
COM server (EXE) waits Applcation.Run before return the control flow to the COM client, so all forms and data modules should be initialized.
However, when a form creates another OLE object (i.e. open Word doc in FormCreate() event handler) this may interrupt waiting and return control to the COM client immediately.
Try to check all initialization code of the forms created before Applcation.Run to localize the problem.