PDA

View Full Version : TThread.OnTerminate with Window properties



Michalson
23-11-2002, 11:43 PM
Help for TThread.OnTerminate Event
Occurs after the thread's Execute method has returned and before the thread is destroyed.

property OnTerminate: TNotifyEvent;

Description

Write an OnTerminate event handler to execute code after the thread finishes executing. The OnTerminate event handler is called in the context of the main VCL thread, which means VCL methods and properties can be called freely. The thread object may also be freed within the event handler.


Assuming this to be true I made an procedure:

procedure TFileSearch.OnTerminate(Sender: TObject);
begin
SearchThread.Free;
SearchThread:=nil;
if Assigned(OnCompletion) then OnCompletion(Self);
end;


And assigned OnCompletion to point to this:

procedure TForm1.OnDone(Sender: TObject);
begin
Caption:='Done '+IntToStr(ListBox1.Items.Count)+', time: '+IntToStr(GetTickCount-StartTime);
end;


With this in place my test app will cause a very nasty EWin32Error exception (Access is denied) about 90% of the time (if I don't run it for a few minutes the first execution will be fine, but all later executions will all crash with this error).

There cause of the error is in the "Caption:='Done '+IntT...." statement. If I remove that statement everything works flawlessly. If I insert test code to retrieve the other values, such as this:


I:=GetTickCount-StartTime;
S:=IntToStr(ListBox1.Items.Count);


It still works fine. However using this:

Caption:='Done';

Will cause the crash, leading me to believe that for some reason OnTerminate is (despite what the help says) not 100% VCL safe, or at least not safe for the Win32 method which is used to change the caption of a Window (not a part of the VCL, but certainly used by the TForm VCL object).

So, has anyone else had any experience, and does anyone know a workaround?

Michalson
24-11-2002, 12:28 AM
I've created a workaround by adding a method to both the thread class(SearchThread) and container (FileSearch) which is called using Synchronize at the end of TThread.Execute. Though I would still be interested to know why OnTerminate cannot access some/all VCL components, despite the help saying so (I would also assume OnTerminate is called in the main thread, though perhaps that assumption is incorrect)

Alimonster
24-11-2002, 01:13 AM
A little investigation of the VCL source showed this, which is the main thread proc:

function ThreadProc(Thread: TThread): Integer;
var
FreeThread: Boolean;
begin
try
if not Thread.Terminated then
try
Thread.Execute;
except
Thread.FFatalException := AcquireExceptionObject;
end;
finally
FreeThread := Thread.FFreeOnTerminate;
Result := Thread.FReturnValue;
Thread.FFinished := True;
Thread.DoTerminate;
if FreeThread then Thread.Free;
EndThread(Result);
end;
end;
It executes the TThread.Execute method then does the clearing up later, which is the interesting part. The DoTerminate is called using synchonize:

procedure TThread.CallOnTerminate;
begin
if Assigned(FOnTerminate) then FOnTerminate(Self);
end;

procedure TThread.DoTerminate;
begin
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;

Since it has the synchonize call there, it should be safe. One suggestion: what happens if you change your OnTerminate to look like this:

procedure TFileSearch.OnTerminate(Sender: TObject);
begin
if Assigned(OnCompletion) then OnCompletion(Self);
FreeAndNil(SearchThread);
end;

?

Does your thread have FreeOnTerminate set to true?
Do you suspend your thread at the end of your execute (this could probably spell bad news if you free it before it gets resumed, not sure)?

I'll try to get an example of this running safely tomorrow (time for bed). Off-hand, I couldn't say what's blowing up the app.

Alimonster
24-11-2002, 12:59 PM
I've uploaded some code here (http://www.alistairkeys.co.uk/download/threadtest.zip) (3K) that searches for files in the C drive (root only, no subdirs). During the search, a label's caption is changed. Once the thread has finished, some labels are changed in the OnTerminate proc for the thread. You can also change the caption there without it blowing up on you, hopefully.

I used FreeOnTerminate = True for the threads. I can't get the thing to crash at the mo, so I'm assuming it's pretty solid. Perhaps the problem you had was related to something else (freeing it inside the OnTerminate? Not checking for Terminated in your thread's Execute often enough?).

Some notes for the code:

It only does the main c: drive. If you want subdirs, I'd recommend pushing the full subdir paths onto a stack (a TStringList) instead of using recursion, which will make it easier for the thread to terminate nicely. Keep on looping through each dir for every file. At the end of each dir, pop a path from the stack. Continue until the stack is empty.

There's a MessageBox commented out - uncomment if you want to know the thread is dying.

It's only been tested under Windows ME. I've not yet tried 2K, but I'm assuming it will be okay there.

Of course, it goes much quicker when you *don't* change a label during the search.

I'm explicitly killing the thread if required in the containing class' destructor. This safely covers closing the app during a search. I'm not sure what would happen if you relied on FreeOnTerminate - something to investigate.

The usual "caveat emptor", etc., disclaimers apply.

Michalson
24-11-2002, 03:03 PM
Actually I already had that functionality (the changing of the caption was entirely up to the end programmer), it was only the OnTerminate event that was causing trouble. Perhaps if I add some commenting I'll upload my VCL component, its a VCL component that allows you to specify any number of filters, recursive search option, adjustable update interval (so that you can start processing files found in another thread), ability to abort search and the ability to do a search with or without a wait (without wait is basically the same as a single threaded search)