Warm tip: This article is reproduced from serverfault.com, please click

Delphi 10.4 FMX App replacing Application.ProcessMessage() with Thread

发布于 2020-11-27 22:48:36

I am trying to run a simple example Delphi 10.4 FMX program that does away with Application.ProcessMessages() as suggested by Android and Application.ProcessMessages and Does application.processmessages behaviour differ between VCL and FMX?.

The button simply updates the label 3 times. Following Background Operations on Delphi Android, with Threads and Timers, it works as expected with one level, but not two levels of change.

Can someone point out what I am doing wrong? I have left the code with Application.ProcessMessages() to show the desired effect.

function Measure1sec(wait: integer): boolean;
var
  sw: TStopwatch;
begin
  sw := TStopwatch.StartNew;
  while sw.ElapsedMilliseconds < wait do sw.ElapsedMilliseconds;
  Result := TRUE;
end;
    
procedure TForm1.EndProgress(Sender: TObject);
var
  Thread: TThread;
begin
  Measure1sec(500);
  label1.text := 'orange';
end;
    
procedure TForm1.Button1Click(Sender: TObject);
var
  Thread: TThread;
begin
  {
  //////////////////  Using Application.ProcessMessages //////////////
  label1.text := 'apple';
  // Application.ProcessMessages;   // with this works without not
  Measure1sec(500);
  label1.text := 'orange';
  // Application.ProcessMessages;   // with this works without not
  Measure1sec(500);
  label1.text := 'done';
  // Application.ProcessMessages;   // with this works without not
  Measure1sec(500);
  }
    
  //////////////////  Using Threads //////////////////////////////////
  Thread := TThread.CreateAnonymousThread(
    procedure
    begin
      TThread.Synchronize(TThread.Current,
        procedure
        begin
          label1.text := 'apple';
        end
      );
    end
  );
  Thread.OnTerminate := EndProgress;
  Thread.Start;
end;
Questioner
na38
Viewed
11
Remy Lebeau 2020-11-28 08:29:25

First off, your Measure1sec() function is inefficient. Instead of using TStopWatch in a busy loop, it can be re-written to use TThread.Sleep() instead, eg:

procedure Measure1sec(wait: integer); inline;
begin
  TThread.Sleep(wait);
end;

In which case, you may as well just get rid of Measure1sec() altogether, eg:

label1.text := 'apple';
// Application.ProcessMessages;
TThread.Sleep(500);
label1.text := 'orange';
// Application.ProcessMessages;
TThread.Sleep(500);
label1.text := 'done';
// Application.ProcessMessages;
TThread.Sleep(500);

Now, to answer your question - you are not seeing all of the label updates because your worker thread performs only 1 label update. For what you are attempting, you would need to have the thread perform multiple label updates, eg:

procedure TForm1.Button1Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread(
    procedure
    begin
      TThread.Synchronize(nil,
        procedure
        begin
          label1.text := 'apple';
        end
      );
      TThread.Sleep(500);
      TThread.Synchronize(nil,
        procedure
        begin
          label1.text := 'orange';
        end
      );
      TThread.Sleep(500);
      TThread.Synchronize(nil,
        procedure
        begin
          label1.text := 'done';
        end
      );
      TThread.Sleep(500);
    end
  ).Start;
end;

However, this is largely a waste of a worker thread, since most of the work is being performed in the main UI thread. As such, I would suggest getting rid of the TThread altogether, use TThread.ForceQueue() instead, eg:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Step1;
end;

procedure TForm1.Step1;
begin
  label1.text := 'apple';
  TThread.ForceQueue(nil, Step2, 500);
end;

procedure TForm1.Step2;
begin
  label1.text := 'orange';
  TThread.ForceQueue(nil, Step3, 500);
end;

procedure TForm1.Step3;
begin
  label1.text := 'done';
end;

Alternatively:

procedure TForm1.Button1Click(Sender: TObject);
begin
  label1.text := 'apple';
  TThread.ForceQueue(nil,
    procedure
    begin
      label1.text := 'orange';
    end,
    500
  );
  TThread.ForceQueue(nil,
    procedure
    begin
      label1.text := 'done';
    end,
    1000
  );
end;