Made some changes to my last example:
1. As noted by @rvk, threads should not be terminated inside their events. So changed FreeAndNil with FThread := nil, and added FreeOnTerminate := True.
2. The procedure Sleep allows system to switch the threads. A more realistic situation is when the thread itself performs continuous work. Replaced with SpinWait. And Max increased, otherwise it's too fast.
3. If the main thread uses shared variables, then inside the TestThread we can skip changes them right now. It is possible next time, all the same already became not actual. Therefore, not unconditional lock but just an attempt. Because IReadWriteSync does not contain the required function, it is replaced by the use of TRTLCriticalSection (TryEnterCriticalSection).
4. To reduce the time for locking the shared variables inside OnTimer, copy them to local ones, and then apply them to the controls.
5. As noted by @engkin, "Calling Terminate and WaitFor is redundant as TThread.Free leads to calling both", so replace it with FThread.Free (this works for nil as well).
6. Set Timer interval to 200ms. It's pretty fast from the human point of view, but very rarely, from the processor's point of view. In addition eliminated situation, when common variables are requested in OnTimer more often than they change in the TestThread, otherwise the TryEnterCriticalSection call can never be executed.
7. And in the end, microoptimization, due to the replacement of Format with FmtStr, eliminates the try finally block for the temporary string variable.
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, ComCtrls, ExtCtrls, StdCtrls;
type
TTestThread = class(TThread)
private
FInfo: string;
FMax: Integer;
FProgress: Integer;
FCriticalSection: TRTLCriticalSection;
protected
procedure Execute; override;
public
constructor Create(AMax: Integer; ACriticalSection: TRTLCriticalSection);
property Progress: Integer read FProgress;
property Info: string read FInfo;
end;
TForm1 = class(TForm)
Label1: TLabel;
ProgressBar1: TProgressBar;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FCriticalSection: TRTLCriticalSection;
FThread: TTestThread;
procedure ThreadTerminated(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
constructor TTestThread.Create(AMax: Integer; ACriticalSection: TRTLCriticalSection);
begin
inherited Create(False);
FMax := AMax;
FCriticalSection := ACriticalSection;
end;
procedure TTestThread.Execute;
begin
while not Terminated and (FProgress <= FMax) do
begin
SpinWait(7000); // Hide some real work
if TryEnterCriticalsection(FCriticalSection) <> 0 then
try
Inc(FProgress);
FmtStr(FInfo, 'Pos: %d, Total: %.1f%%', [FProgress, FProgress * 100/ FMax]);
finally
LeaveCriticalsection(FCriticalSection);
end;
end;
end;
procedure TForm1.ThreadTerminated(Sender: TObject);
begin
FThread := nil;
ProgressBar1.Position := ProgressBar1.Max;
Label1.Caption := 'Complete!';
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
LPosition: Integer;
LCaption: string;
begin
if Assigned(FThread) then
begin
EnterCriticalsection(FCriticalSection);
try
LPosition := FThread.Progress;
LCaption := FThread.Info;
finally
LeaveCriticalsection(FCriticalSection);
end;
ProgressBar1.Position := LPosition;
Label1.Caption := LCaption;
end
else
Timer1.Enabled := False;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ProgressBar1.Max := 200000;
Timer1.Interval := 200;
InitCriticalSection(FCriticalSection);
FThread := TTestThread.Create(ProgressBar1.Max, FCriticalSection);
FThread.FreeOnTerminate := True;
FThread.OnTerminate := @ThreadTerminated;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FThread.Free;
DoneCriticalsection(FCriticalSection);
end;
end.
Please check in Linux.
In my opinion, this is a handy scenario when the work thread performs a lot of work, with almost no delays, while recording the current state in shared variables as quickly as it can. And the main thread displays the state of this data, but in order not to load the processor, it does it slowly (with the help of a timer), but visually fast enough.