Why can't I call CheckTerminated on an externally created thread? - delphi

When I run this code:
FMyThread := TThread.createAnonymousThread(
procedure
begin
while not FMyThread.CheckTerminated do begin
sleep(750);
TThread.synchronize(nil,
procedure
begin
if FMyThread.CheckTerminated then exit;
....
end);
end;
end);
FMyThread.start;
I have the exception
Cannot call CheckTerminated on an externally created thread
Why ?

If you look at the implementation of CheckTerminated, you will notice it's not a method but a static class method.
CheckTerminated indicates if the currently executing thread has its Terminated flag set to TRUE. When you call FMyThread.CheckTerminated inside TThread.synchronize, , it doesn't check FMyThread.Terminated, it tries to check the state of "GetCurrentThreadId", which in this case is your process' main thread.
Since the main thread isn't created by a TThread, the RTL creates a TExternalThread (See class function TThread.GetCurrentThread: TThread)
In other words, if you have a TThread object and want to check whether it's terminated or not, you should call Terminated, not CheckTerminated.
Since AnonymousThread are by default freed on terminate, you need to make sure you clear any reference to your thread in a OnTerminate handler.

Related

Accessing TObject instance when it is locked by TMonitor.Enter in the calling thread

What will happen if a thread tries to access the same object locked by another thread? I know it can be handled by TMonitor.Wait(), but what if there is no handling codes to check if it is locked? Will there be an error?
In the example below, Thread1Process locks the object and Thread2Process attempts to assign a value to the object's property. Will the Thread2Process automatically wait before Thread1Process releases the lock to execute the next line var x: Integer := 1; or will it stop and throw an exception?
procedure Thread1Process(const AObject: TObjectDescendant);
begin
TMonitor.Enter(AObject);
try
// lengthy process here...
finally
TMonitor.Exit(AObject);
end;
end;
procedure Thread2Process(const AObject: TObjectDescendant);
begin
AObject.StringProperty := 'Any value';
var x: Integer := 1;
end;
We are using Delphi 11 Alexandria.
TMonitor is just a synchronization lock, nothing more. Much like TMutex, TSemaphore, etc.
It doesn't do anything to the object itself. If one thread decides to enter the lock, and a second thread doesn't, the second thread will not be blocked in any way, and no exception will be raised 1, but there is no guarantee to the stability of the object or its members. Race conditions occur due to lack of proper synchronization by all involved threads cooperating with each other.
1: unless the object itself decides to raise an exception, or a system exception is raised, like from accessing invalid memory, etc.
On a side note, your call to TMonitor.Enter() needs to be outside the try block, eg:
procedure Thread1Process(const AObject: TObjectDescendant);
begin
TMonitor.Enter(AObject);
try
// lengthy process here...
finally
TMonitor.Exit(AObject);
end;
end;

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.

How to check if the Application.MainForm is valid?

How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it (from a MadExcept ExceptionHandler).
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
I was thinking:
if Assigned(Application)
and (not Application.Terminated)
and Assigned(Application.MainForm)
and Application.MainForm.HandleAllocated then
begin
PostMessage(Application.MainForm.Handle, MyMessage, 0, 0);
end;
Is this correct?
How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it.
OK.
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
Uh oh.
....
Is this correct?
No it certainly is not. Your code can never be made threadsafe because it is not permitted to access VCL objects from outside the main thread.
In your particular case consider the following sequence of events:
You perform your tests in the if culminating in your evaluating Application.MainForm.HandleAllocated as True. Ignore for a moment the fact that you are doing this outside the main thread.
You then set about preparing the call to PostMessage. But at this very instance, the main form is destroyed.
By the time your thread gets round to accessing Application.MainForm, it has gone.
You are going to need to work a little harder here. You'll need to do something like this:
// interface section of some unit
procedure RegisterMainFormHandle(Wnd: HWND);
procedure UnregisterMainFormHandle;
procedure PostMessageToMainForm(...);
// implementation section
var
MainFormHandleLock: TCriticalSection;
MainFormHandle: HWND;
procedure RegisterMainFormHandle(Wnd: HWND);
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := Wnd;
finally
MainFormHandleLock.Release;
end;
end;
procedure UnregisterMainFormHandle;
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := 0;
finally
MainFormHandleLock.Release;
end;
end;
procedure PostMessageToMainForm(...);
begin
MainFormHandleLock.Acquire;
try
if MainFormHandle <> 0 then
PostMessage(MainFormHandle, ...)
finally
MainFormHandleLock.Release;
end;
end;
You also need to create and destroy the critical section, but I assume that you know how to do that.
In your main form you override CreateWnd and DestroyWnd and arrange that they call RegisterMainFormHandle and UnregisterMainFormHandle.
Then you can call PostMessageToMainForm from any thread at any time.
Of course, if the main form's window is recreated then you'll lose some messages. Which sounds like it could be a problem. Using AllocateHwnd to have a window whose lifetime you control is usually a better option than using the main form's window like this.
Make some global variable flag = false at the beginning.
Make your mainform turn it to true.
Check that flag to see if main form was already initialised or not yet.
You can make it from such places as mainform's OnActivate event or overridden TMainForm.Loaded method
Similarly when your application would be terminating and the mainform would get hidden (and later even destroyed) - you would reset the flag back to false

Thread Error: The Handle is Invalid (6) when trying to Free a suspended thread

In a given example I am receiving an exception when calling AThread.Free.
program Project44;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes, Windows;
type
TMyException = class(Exception);
var
AThread: TThread;
begin
AThread := TThread.Create(True);
try
AThread.FreeOnTerminate := True;
//I want to do some things here before starting the thread
//During the setup phase some exception might occur, this exception is for simulating purpouses
raise TMyException.Create('exception');
except
AThread.Free; //Another exception here
end;
end.
I have two questions:
How should I free AThread instance of TThread in a given example?
I don't understand, why TThread.Destroy is calling Resume before destroing itself. What is the point of this?
You can't set FreeOnTerminate to True and call Free on the thread instance. You have to do one or the other, but not both. As it stands your code destroys the thread twice. You must never destroy an object twice and of course when the destructor runs for the second time, errors occur.
What happens here is that since you created the thread suspended, nothing happens until you explicitly free the thread. When you do that the destructor resumes the thread, waits for it to complete. This then results in Free being called again because you set FreeOnTerminate to True. This second call to Free closes the handle. Then you return to the thread proc and that calls ExitThread. This fails because the thread's handle has been closed.
As Martin points out in the comment you must not create TThread directly since the TThread.Execute method is abstract. Also, you should not use Resume which is deprecated. Use Start to begin execution of a suspended thread.
Personally I don't like to use FreeOnTerminate. Using this feature results in the thread being destroyed on a different thread from which it was created. You typically use it when you want to forget about the instance reference. That then leaves you uncertain as to whether or not the thread has been destroyed when your process terminates, or even whether it is terminating and freeing itself during process termination.
If you must use FreeOnTerminate then you need to make sure that you don't call Free after having set FreeOnTerminate to True. So the obvious solution is to set FreeOnTerminate to True immediately before after calling Start and then forget about the thread instance. If you have any exceptions before you are ready to start then you can safely free the thread then since you FreeOnTerminate would still be False at that point.
Thread := TMyThread.Create(True);
Try
//initialise thread object
Except
Thread.Free;
raise;
End;
Thread.FreeOnTerminate := True;
Thread.Start;
Thread := nil;
A more elegant approach would be to move all the initialisation into the TMyThread constructor. Then the code would look like this:
Thread := TMyThread.Create(True);
Thread.FreeOnTerminate := True;
Thread.Start;
Thread := nil;
The situation is very complicated in your case.
First, you does not actually free a suspended thread; a thread is resumed in destructor:
begin
Terminate;
if FCreateSuspended then
Resume;
WaitFor;
end;
Since Terminate is called before Resume, the Execute method never runs, and thread terminates immediately after being resumed:
try
if not Thread.Terminated then
try
Thread.Execute;
except
Thread.FFatalException := AcquireExceptionObject;
end;
finally
Result := Thread.FReturnValue;
FreeThread := Thread.FFreeOnTerminate;
Thread.DoTerminate;
Thread.FFinished := True;
SignalSyncEvent;
if FreeThread then Thread.Free;
Now look at the last line - you call destructor (Thread.Free) from destructor itself!
Fantastic bug!
To answer your questions:
You just can't use FreeOnTerminate:= True in your code;
You should ask Embarcadero why TThread is designed so; my guess - some code
(DoTerminate method) should be executed in thread context while
thread terminates.
You can send a feature request to QC: add FFreeOnTerminate:= False to TThread.Destroy implementation:
destructor TThread.Destroy;
begin
FFreeOnTerminate:= False;
// everything else is the same
..
end;
That should prevent recursive desctructor call and make your code valid.

Deadlock when closing thread

I created a class that opens a COM port and handles overlapped read and write operations. It contains two independent threads - one that reads and one that writes data. Both of them call OnXXX procedures (eg OnRead or OnWrite) notifying about finished read or write operation.
The following is a short example of the idea how the threads work:
TOnWrite = procedure (Text: string);
TWritingThread = class(TThread)
strict private
FOnWrite: TOnWrite;
FWriteQueue: array of string;
FSerialPort: TAsyncSerialPort;
protected
procedure Execute; override;
public
procedure Enqueue(Text: string);
{...}
end;
TAsyncSerialPort = class
private
FCommPort: THandle;
FWritingThread: TWritingThread;
FLock: TCriticalSection;
{...}
public
procedure Open();
procedure Write(Text: string);
procedure Close();
{...}
end;
var
AsyncSerialPort: TAsyncSerialPort;
implementation
{$R *.dfm}
procedure OnWrite(Text: string);
begin
{...}
if {...} then
AsyncSerialPort.Write('something');
{...}
end;
{ TAsyncSerialPort }
procedure TAsyncSerialPort.Close;
begin
FLock.Enter;
try
FWritingThread.Terminate;
if FWritingThread.Suspended then
FWritingThread.Resume;
FWritingThread.WaitFor;
FreeAndNil(FWritingThread);
CloseHandle(FCommPort);
FCommPort := 0;
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Open;
begin
FLock.Enter;
try
{open comm port}
{create writing thread}
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Write(Text: string);
begin
FLock.Enter;
try
{add Text to the FWritingThread's queue}
FWritingThread.Enqueue(Text);
finally
FLock.Leave;
end;
end;
{ TWritingThread }
procedure TWritingThread.Execute;
begin
while not Terminated do
begin
{GetMessage() - wait for a message informing about a new value in the queue}
{pop a value from the queue}
{write the value}
{call OnWrite method}
end;
end;
When you look at the Close() procedure, you will see that it enters the critical section, terminates the writing thread and then waits for it to finish.
Because of the fact that the writing thread can enqueue a new value to be written when it calls the OnWrite method, it will try to enter the same critical section when calling the Write() procedure of the TAsyncSerialPort class.
And here we've got a deadlock. The thread that called the Close() method entered the critical section and then waits for the writing thread to be closed, while at the same time that thread waits for the critical section to be freed.
I've been thinking for quite a long time and I didn't manage to find a solution to that problem. The thing is that I would like to be sure that no reading/writing thread is alive when the Close() method is left, which means that I cannot just set the Terminated flag of those threads and leave.
How can I solve the problem? Maybe I should change my approach to handling serial port asynchronously?
Thanks for your advice in advance.
Mariusz.
--------- EDIT ----------
How about such a solution?
procedure TAsyncSerialPort.Close;
var
lThread: TThread;
begin
FLock.Enter;
try
lThread := FWritingThread;
if Assigned(lThread) then
begin
lThread.Terminate;
if lThread.Suspended then
lThread.Resume;
FWritingThread := nil;
end;
if FCommPort <> 0 then
begin
CloseHandle(FCommPort);
FCommPort := 0;
end;
finally
FLock.Leave;
end;
if Assigned(lThread) then
begin
lThread.WaitFor;
lThread.Free;
end;
end;
If my thinking is correct, this should eliminate the deadlock problem. Unfortunately, however, I close the comm port handle before the writing thread is closed. This means that when it calls any method that takes the comm port handle as one of its arguments (eg Write, Read, WaitCommEvent) an exception should be raised in that thread. Can I be sure that if I catch that exception in that thread it will not affect the work of the whole application? This question may sound stupid, but I think some exceptions may cause the OS to close the application that caused it, right? Do I have to worry about that in this case?
Yes, you should probably reconsider your approach. Asynchronous operations are available exactly to eliminate the need for threads. If you use threads, then use synchronous (blocking) calls. If you use asynchronous operations, then handle everything in one thread - not necessarily the main thread, but it doesn't make sense IMO to do the sending and receiving in different threads.
There are of course ways around your synchronization problem, but I'd rather change the design.
You can take the lock out of the Close. By the time it returns from the WaitFor, the thread body has noticed it has been terminated, completed the last loop, and ended.
If you don't feel happy doing this, then you could move setting the lock just before the FreeAndNil. This explicitly lets the thread shutdown mechanisms work before you apply the lock (so it won't have to compete with anything for the lock)
EDIT:
(1) If you also want to close the comms handle do it after the loop in the Execute, or in the thread's destructor.
(2) Sorry, but your edited solution is a terrible mess. Terminate and Waitfor will do everything you need, perfectly safely.
The main problem seems to be that you place the entire content of Close in a critical section. I'm almost sure (but you'll have to check the docs) that TThread.Terminate and TThread.WaitFor are safe to call from outside the section. By pulling that part outside the critical section you will solve the deadlock.

Resources