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;
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;
Thanks Remy, this is perfect. I think I will keep the thread, your middle solution, as I need to do more than the ui but wanted a simple example. I have accepted the answer.