Reputation: 2050
I want to be able to terminate a thread on the click of a button, e.g., stop the process half-way through if the user wants. Apparently, you can do this by mointoring for Terminated variable in the thread, which means you can then excute some code before exiting rather than abruptly terminating.
The code so far is as follows :-
Start Thread On Click
procedure TForm1.Panel29Click(Sender: TObject);
var
cmpfil : TThread;
begin
if (Edit3.Text <> '') AND (Edit4.Text <> '') then
begin
Form1.ProgressBar1.Min := 0;
Form1.Progressbar1.Max := 30000;
Form1.ProgressBar1.Position := 0;
cmpfiles := TCompareFilesThread.Create();
end;
end;
Create Thread
constructor TCompareFilesThread.Create;
begin
inherited Create(False);
end;
Actual Thread
procedure TCompareFilesThread.Execute;
var
forg, fpat : file;
byteorg, bytepat : Array[0..1023] of byte;
i,z,o : integer;
fil1,fil2 : TFilename;
begin
//Form1.CompareFiles(FEdit3Text, FEdit4Text, FGrid, FOp, FProg);
begin
fil1 := Form1.Edit3.Text;
fil2 := Form1.Edit4.Text;
if Form1.CRCAdlerGenFile(fil1,1) <> Form1.CRCAdlerGenFile(fil2,1) then //Only Run if files arn't same
begin
op := 3;
synchronize(SetOP);
i := 0;
x := 1;
o := 0;
AssignFile(forg,fil1);
FileMode := fmOpenRead;
Reset(forg,1);
AssignFile(fpat,fil2);
FileMode := fmOpenRead;
Reset(fpat,1);
//Set Progress Bar
while NOT eof(forg) do
begin
while Terminated = False do
begin
BlockRead(forg,byteorg,1024);
BlockRead(fpat,bytepat,1024);
for z := 0 to 1023 do
begin
if byteorg[z] <> bytepat[z] then
begin
synchronize(sProgBarNext);
by := bytepat[z];
off := IntToStr(o);
synchronize(SyncGrid);
inc(x);
end;
inc(o);
end;
end;
end;
CloseFile(forg);
CloseFile(fpat);
end;
end;
Free;
end;
I've already added the While Terminated = False do
line which will stop the process when that is changed. I just can't seem to figure out how to change it. I never created that variable; it's the built-in Delphi feature. I have read about TMyThread.Terminate()
however I can't seem to find out exactly what that does. Does it set Terminated to True or will it just kill the thread where it stands?
P.S. I haven't posted the code from the synchronized routines. One prints to StringGrid, one updates ProgressBar and the third sets an op variable which is used by the StringGrid Sync routine, and I don't see this code as being relevant to the problem, however I can post if requested.
Upvotes: 2
Views: 587
Reputation: 13590
Yes, call Terminate on your thread object. It sets Terminated
to true, and that's all it does - your thread needs to check for Terminated
, as your code is doing (although there's a bug, see below.)
In more detail: you create your thread object here:
cmpfiles := TCompareFilesThread.Create();
Make cmpfiles
a member variable of your form or another class, not local to your Panel29Click
procedure. Initialise it to nil
in the constructor. Then when you need to cancel, call:
if Assigned(cmpfiles) then begin
cmpfiles.Terminate;
end;
This sets the Terminated flag if the thread exists. (Edit: thanks Rob, who suggested avoiding FreeOnTerminate. It was bad advice of mine to suggest using it in the first place.) Your thread also needs to notify your main thread when its finished, right at the end of Execute, so that you can free it. One way to do this is to use Synchronize
to notify the main thread, and then in the main thread in the method you pass to Synchronize
you can free the thread object.
FreeAndNil(cmpfiles);
Do this at the very end of Execute
because the thread object will be deleted - you don't want any more code to run.
One bug I spotted: Your Execute
code has the following two while loops:
while NOT eof(forg) do
begin
while Terminated = False do
begin
...
end;
end;
This will actually loop while not terminated, and it will only check if it's at the end of the file once Terminated
is set to true
. You probably want your loop to check both conditions at the same time, something like:
while (not eof(forg) and not Terminated) do
begin
...
end;
Oh, and another bug, your thread code accesses Form1's data:
if Form1.CRCAdlerGenFile(fil1,1) <> Form1.CRCAdlerGenFile(fil2,1) then //Only Run if files arn't same
What is this code, running a CRC on the files? If the CRCAdlerGenFile
function doesn't actually touch any part of Form1 and it only relies on its parameters (and they're standalone), make this either a helper function somewhere - it doesn't have to be part of a class, you can have standalone functions. If it does rely on parts of Form1, you shouldn't be calling it from your thread code.
Upvotes: 5