Flatlyn
Flatlyn

Reputation: 2050

Terminate A Thread, Running Code Before Exit

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

Answers (1)

David
David

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

Related Questions