jimsweb
jimsweb

Reputation: 1082

Why 'form close' event is not happening when a huge for loop is runnig in Delphi?

I am trying out following code. However, if I click on form's close button while this code is running, nothing happens. How can I correct this? I need to close the form even when this loop is executing.

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to 9999999 do
  begin
    Memo1.Lines.Add('hi');
    Application.ProcessMessages;
  end;
end;

Upvotes: 1

Views: 1410

Answers (3)

IceCold
IceCold

Reputation: 21194

You need to run any tight loop in a thread. This will solve the problem.

BUT if you want to keep the code as it is, Application.ProcessMessages will make your loop terribly slow. So you need to run Application.ProcessMessages not so often:

  Counter:= 0; 
  for i := 0 to 9999999 do
  begin
    DoSomeStuff;

    { Prevent freeze }  
    inc(Counter); 
    if counter > 10000 then 
     begin 
       Counter:= 0;
       Application.ProcessMessages;
       if Application.Terminated then Exit;
      end; 
  end;

Upvotes: 1

Mason Wheeler
Mason Wheeler

Reputation: 84640

Have a look at what's going on inside Application.ProcessMessages.

When you close the main form, windows sends a WM_QUIT message to the program. The relevant part of TApplication.ProcessMessages looks like this:

    if Msg.Message <> WM_QUIT then
    begin
      //skipped
    end
    else
    begin
{$IF DEFINED(CLR)}
      if Assigned(FOnShutDown) then FOnShutDown(self);
      DoneApplication;
{$IFEND}
      FTerminate := True;
    end;

I assume this is not a CLR program, so the only thing that happens at this point is setting FTerminate := True on Application. This is reflected in the Application.Terminated property.

When the application shuts down, one of the things it does in order to shut down safely is wait for all threads to finish. This code happens to be running in the main thread, but the principle would be the same in any thread: If you're doing a long-running task that might have to finish early, you have to explicitly check for early termination.

Knowing this, it's easy to figure out how to fix your code:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to 9999999 do
  begin
    Memo1.Lines.Add('hi');
    Application.ProcessMessages;
    if Application.Terminated then
      Break;
  end;
end;

Also, beware of using Application.ProcessMessages in the first place, as it will process all messages for the application. For a simple idea of what might go wrong, try adding IntToStr(i) instead of 'hi' to Memo1.Lines, knock a couple of orders of magnitude off the counter, and then click the button two or three times in rapid succession and watch the output...

Upvotes: 14

Uwe Raabe
Uwe Raabe

Reputation: 47769

Check for Apllication Terminated:

  for i := 0 to 9999999 do
  begin
    Memo1.Lines.Add('hi');
    Application.ProcessMessages;
    if Application.Terminated then Exit;
  end;

Upvotes: 6

Related Questions