Delayed execution in Delphi
Asked Answered
D

3

5

Is it possible to start procedure delayed after the calling procedure will end?

procedure StartLoop;
begin
  DoSomething;
end;

procedure FormCreate(...);
begin
  if ParamStr(1)='start' then StartLoop;
end;

StartLoop will be called inside FormCreate, and FormCreate will be waiting, and block further execution not only the of FormCreate itself, but also further procedures executing after it (FormShow, etc.), and form will not show until StartLoop will end.

I need to wait until FormCreate will end, and run StartLoop after that (without using threads).

Dwarfish answered 13/12, 2018 at 5:20 Comment(5)
There are many many possible ways to achieve this and which is best depends on factors that we can't see. I suspect that your accepted answer based on a timer is probably the worst possible solution.Ziegler
Possible dupe: What is the best way to call a procedure “delayed”?Voiture
What's wrong with timer? In "possible dupe" is no answer (answers to this question are more useful). Don't like using windows messages at all.Dwarfish
I selected timer precisely because it is simplest, and works in main thread.Dwarfish
@jsmith, TThread.ForceQueue is working in the main thread and can be called from the main thread.Voiture
F
7

The simplest way is using timer.

Let you create DelayTimer with needed period set and Enabled = False on the form in design time (you can also create it dynamically). Assign event handler for it:

  procedure TFormXX.DelayTimerTimer(Sender: TObject);
  begin
     DelayTimer.Enabled := False;   // works only once
     StartLoop;
  end;

in the form intialization routine start this timer:

 procedure FormCreate(...);
 begin
   if ParamStr(1)='start' then  
       DelayTimer.Enabled := True; 
 end;

Perhaps you want to start the timer later, for example - in the OnShow, if your application performs some continuous actions during creation.

Ferrite answered 13/12, 2018 at 5:36 Comment(2)
This is risky though because now the procedure runs at some unspecified point in the execution flow.Ziegler
@David Heffernan But author did not specified when exactly DoSomething could safely start, so I added the last sentence.Ferrite
Y
14

If you are using 10.2 Tokyo or later, you can use TThread.ForceQueue():

procedure TMyForm.FormCreate(Sender: TObject);
begin
  if ParamStr(1) = 'start' then
    TThread.ForceQueue(nil, StartLoop);
end;

Otherwise, you can use PostMessage() instead:

const
  WM_STARTLOOP = WM_USER + 1;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  if ParamStr(1) = 'start' then
    PostMessage(Handle, WM_STARTLOOP, 0, 0);
end;

procedure TMyForm.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_STARTLOOP then
    StartLoop
  else
    inherited;
end;
Yearning answered 13/12, 2018 at 7:8 Comment(0)
F
7

The simplest way is using timer.

Let you create DelayTimer with needed period set and Enabled = False on the form in design time (you can also create it dynamically). Assign event handler for it:

  procedure TFormXX.DelayTimerTimer(Sender: TObject);
  begin
     DelayTimer.Enabled := False;   // works only once
     StartLoop;
  end;

in the form intialization routine start this timer:

 procedure FormCreate(...);
 begin
   if ParamStr(1)='start' then  
       DelayTimer.Enabled := True; 
 end;

Perhaps you want to start the timer later, for example - in the OnShow, if your application performs some continuous actions during creation.

Ferrite answered 13/12, 2018 at 5:36 Comment(2)
This is risky though because now the procedure runs at some unspecified point in the execution flow.Ziegler
@David Heffernan But author did not specified when exactly DoSomething could safely start, so I added the last sentence.Ferrite
M
2

AN other solution could be wrapping your DoSomething method into a Task:

uses
  System.Threading;

procedure TForm2.DoSomething;
begin
  Sleep(2000);
  Caption := 'Done';
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  if ParamStr(1) = 'start' then
    TTask.Run(
      procedure
      begin
        DoSomething
      end);
end;
Mouthful answered 13/12, 2018 at 5:52 Comment(2)
Thank you. This will run in main thread or in separate thread? Doing something with VCL is OK from the DoSomething, or, because it is separate thread, should not be used?Dwarfish
In which case, you can use TThread.Synchronize() or TThread.Queue() to have the task thread execute code in the main UI thread. But, I would generally recommend using TThread.CreateAnonymousThread() instead of TTask for this. TTask is overkill for such a simple task, nit to mention TTask is notorioisly buggy in general.Yearning

© 2022 - 2024 — McMap. All rights reserved.