Why Synchronize is being locked by ShowModal? - delphi

I created a generic thread class that controls a progress form which is injected in the constructor of the thread and set as _progressForm. At the Execute method, the thread initializes the form and shows it using the function ShowModal() as shown below:
procedure TProgressThread.Execute;
begin
...
ShowForm;
end;
procedure TProgressThread.ShowForm;
begin
if Assigned(_progressForm) then
begin
Synchronize(
procedure
begin
_progressForm.ShowModal();
end);
end;
end;
What I can't understand is why my thread is locked at Synchronize? It doesn't return until the progress form is closed. Shouldn't ShowModal only lock the main thread?

TThread.Synchronize() is synchronous. It blocks the calling thread until the synced code returns from the main thread.
ShowModal() is also synchronous. It blocks the calling thread until the Form is closed.
So, when Synchronize() calls ShowModal() in the main thread, Synchronize() will not return to the worker thread until the Form is closed.
If you don't want to block the worker thread, either use TThread.Queue() instead of TThread.Synchronize(), or use TForm.Show() instead of TForm.ShowModal().
Display of progress should not block the worker thread from doing its work. You should have the thread asynchronously post progress updates to the main thread, and let the main thread decide how to display the status while the thread continues with its work. The worker thread should have no knowledge of the UI at all.

Related

Do timer events in Delphi happen in their own thread?

When a Delphi timer executes, is it not in the main thread?
procedure TMainForm.MyTimerTimer(Sender: TObject);
begin
MyModalDialog.StatusText.BeginUpdate;
MyModalDialog.StatusText.Text := 'timer fired...';
MyModalDialog.StatusText.EndUpdate;
end;
I am wondering if crashes here are due to updating the GUI elements outside of the main thread.
The timer event handler execute in the context of the thread having created it. Usually it is the main thread but you can create a timer within any thread.
The timer will execute on the main thread. No need to worry about using Synchronize().

Is there a way to do TThread.Synchronize with Timeout?

Sometimes you need to call TThread.Synchronize, but also you can enter in a deadlock situation.
For example:
Thread1.execute
procedure
begin
....
TThread.Synchronize(..)
...
end;
Then from the main thread doing something like:
Thread1.terminate;
Thread1.waitfor;
can leave to some deadlock because TThread.Synchronize will never succeed :(
I would like to know the most easy way to handle such scenario.
There is no way to use TThread.Synchronize() with a timeout.
You can use TThread.Queue() instead, passing it a procedure associated with a TEvent that you can wait on after Queue() exits. Then the main thread can signal that TEvent when it processes the request (just be sure to not free the TEvent until after the main thread has used it, unless you call TThread.RemoveQueuedEvents() to cancel the request first).
That being said, what you describe is NOT a deadlock scenario. If TThread.WaitFor() is called in the main UI thread, it processes pending Synchronize()/Queue() requests while waiting for the thread to terminate. If TThread.WaitFor() is called in another thread, then the main UI thread is free to process Synchronize()/Queue() requests normally.

Why does the doc say not to call Synchronize from the main thread? [duplicate]

I am using CreateAnonymousThread for a worker task, and when I started with it I used Synchronize within the entire declaration as per documented examples, e.g:
procedure Txxx.RunWorker;
begin
FExecutionThread := TThread.CreateAnonymousThread(procedure ()
begin
TThread.Synchronize (TThread.CurrentThread,
procedure ()
begin
// Here before worker stuff
NotifyBeforeWorkerStuff;
end);
// Do worker stuff
TThread.Synchronize (TThread.CurrentThread,
procedure ()
begin
// Here after worker stuff
NotifyAfterWorkerStuff;
end);
end);
FExecutionThread.Start;
end;
end;
As you see, from within this thread I launch event notifications to various parts of my app including VCL forms (NotifyBeforeWorkerStuff etc).
Later, I saw that I could move Synchronize() more locally to each VCL form close to the point that actually required it for updating (non-safe) VCL controls:
procedure TSomeVCLForm.ReceiveNotification;
begin
TThread.Synchronize (TThread.CurrentThread,
procedure ()
begin
Label1.Caption := GetSomeStringFunction;
end);
end;
The worker thread then becomes simpler as long as I live with notifications being from either main or worker threads:
procedure Txxx.RunWorker;
begin
FExecutionThread := TThread.CreateAnonymousThread(procedure ()
begin
NotifyBeforeWorkerStuff;
// Do worker stuff
NotifyAfterWorkerStuff;
end);
FExecutionThread.Start;
end;
I have several questions about whether this is correct:
My notifications may be from the worker thread but also from the main thread (e.g derived from a button press). So, when 'ReceiveNotification' on a VCL form is called from the main thread, is it allowed to call TThread.Synchronize as above? The the XE8 docs imply not, but checking System.Classes this looks ok and it works fine.
Within 'ReceiveNotification' when Label1.Caption fetches the string from GetSomeStringFunction, is it correct that there is absolutely no need for locking within that function even when the call is from a worker thread?
Thanks for any advice.
The documentation says:
Warning: Do not call Synchronize from within the main thread. This can cause an infinite loop.
I think that documentation is simply wrong. The implementation in XE8 checks whether or not the current thread is the main thread. If it is then the method is executed directly.
No locking is required in ReceiveNotification because the call to GetSomeStringFunction is always performed on the main thread.

ProcessMessages and use of application

I need to know if use of ProcessMessages that allow me to use entire application is legal.
Pseudo code:
Main thread button call - search.
procedure ButtonOnClick;
begin
var1 = ExecuteSearch();
end;
function ExecuteSearch:Something;
begin
thread.StartThread;
while thread.Finished do
Application.ProcessMessages;
result := something;
end;
When I use this construction I can click other parts of my software and use it. But I don't know how this works. And if its safe.
Whilst this can be made safe, you are playing with fire. You run the risk of re-entrancy. You have to make sure that the user cannot press the button again. I trust you have disabled it whilst the search is running. You must make sure that it is disabled before you first call ProcessMessages.
My advice would always be to avoid using ProcessMessages. Better would be to start the thread and arrange for it to notify the main thread when it is done. Of course, you still need to disable the button whilst the thread is running.
However, if you really must use ProcessMessages don't do it with a busy loop like this. There's not much point using an entire processor to wait for a long running search operation to complete. Use a more intelligent blocking loop like this:
while MsgWaitForMultipleObjects(1, Thread.Handle, False,
INFINITE, QS_ALLEVENTS)=WAIT_OBJECT_0+1 do
Application.ProcessMessages;
The MsgWaitForMultipleObjects function will block until either:
A message is placed on the queue, or
The thread is signaled. The thread is signaled when it is complete.
The loop terminates when the thread is signaled, but also processes any queued messages.
Though the code is safe, what you could also do is use the OnTerminate event on the thread you're starting. This way you let Delphi control how to post back from the background thread to the main thread. Internally it uses the thread's Synchronize method, which you can use yourself to let the thread post intermediate progress information to the main thread.

how to get the information that did when thread is paused

how can i get actions ,i did when my thread is paused after resumed (Sorry for my bad english )
ok i will explain with code
function mythreadf(p:Pointer):DWORD stdcall;
var i:Integer;
begin
for i:=0 to 1000000 do begin
if myevent.WaitFor(INFINITE)=wrsignaled
then
begin
if Form1.RadioButton1.Checked then ShowMessage('Checked');
Form1.Label1.Caption:=IntToStr(i);
end;
end;
end;
i am pausing and resuming my thread using resetevent and setevent
after i paused my thread by clicking resetevent button and then i checked radiobotton1 after that when resume my thread by using setevent again .dont send error occuring and applications closing :(
can any one help me in this issue
regards
Edit 1:
Error image
http://i49.tinypic.com/11r7nkn.jpg
Accessing VCL UI controls directly in a worker thread is NOT thread-safe (even ShowMessage() is not thread-safe. Use the Win32 API MessageBox() directly instead). All kinds of bad things can happen, including crashes. You must delegate your UI access to the main thread instead. The TThread class has a Synchronize() method for that purpose. Or you can use any other inter-thread synchronization of your choosing, such as by using SendMessage() to send custom messages to a hidden window created in the main thread via AllocateHWnd() or CreateWindow/Ex().
Your thread, as Remy says, should not be accessing the checkbox. Also it's bad programming style. Your background thread has a purpose? That purpose will help you find a name. If you create a class that inherits from TThread, you will get farther, faster.
interface
type
TMyElephantCountingThread = class(TThread)
protected
FResultStr:String; // holds something for later display on the user interface
FOptionChecked:Boolean; // set from main thread, to tell background thread whether or not a checkbox option is checked.
....
end;
....
implementation
....
function TMyElephantCountingThread.ElephantCounterResults;
begin
// all data fields in here is local to this thread
if FOptionChecked then
FResultStr := IntToStr(FIntegerValue);
end;
As you can see my code above uses only fields that belong to my thread object.
If FOptionChecked needs to be set equal to the value of Checkbox.checked, that must be done in the main thread.
You can not copy and paste write code from your foreground thread (which can access your VCL objects) into your background thread code (which can not safely access those objects), and not expect problems.

Resources