问题
I've used the Delphi many years ago, I remember that in version 7 and even then I did not have much knowledge, I worked with C++ since then and I am currently working in a company using Delphi XE10 and realized that searching I found a lot of random things of difference versions and that makes me a little confused.
I'm playing with threads and I wanted to update the GUI and searching I found the Synchronize
and the way in which it works shows the impact it has on performance, you can notice the application freezing.
I was wondering if there is a smoother way to handle this, something like events, notifications or whatever?
@edit
unit WriterThreadUnit;
interface
uses
System.Classes, System.SysUtils, Unit1;
type
TWriterThread = class(TThread)
private
linesToPrint: integer;
fileDirectory: string;
function generateFilename(): string;
procedure write();
protected
procedure Execute; override;
public
constructor Create
(
const createSuspended: boolean;
const linesToPrint: integer;
fileDirectory: string
);
end;
implementation
{ TWriterThread }
constructor TWriterThread.Create
(
const createSuspended: boolean;
const linesToPrint: integer;
fileDirectory: string
);
begin
Self.linesToPrint := linesToPrint;
Self.fileDirectory := fileDirectory;
Self.FreeOnTerminate := true;
inherited Create(CreateSuspended);
end;
procedure TWriterThread.Execute;
begin
inherited;
write;
end;
function TWriterThread.generateFilename: string;
begin
Result := Format('%s\%s_total_lines_%d.txt',
[
Self.fileDirectory,
FormatDateTime('hh-mm-ss-zzz', Now),
self.linesToPrint
]
);
end;
procedure TWriterThread.write;
var
fileLines: TStringList;
i: integer;
filename: string;
begin
fileLines := TStringList.Create;
filename := generateFilename;
try
for I := 1 to Self.linesToPrint do
begin
Synchronize(
procedure
begin
Form1.Memo1.Lines.Add('Writing: ' + IntToStr(I) + ' to ' + generateFilename);
end
);
fileLines.Add(Format('Line number: %d', [i]));
Sleep(1);
end;
fileLines.SaveToFile(filename);
finally
fileLines.Free;
end;
end;
end.
回答1:
Your code is not wrong - it just spends a lot of time in many, many small VCL GUI updates.
I suspect that Memo redrawing is not fast procedure (note BeginUpdate/EndUpdate
influence when Memo is filled with thousands of strings line-by-line). So make sync calls as seldom as possible (sending string packs, if needed). Indeed, user is not capable to see 1000 updates in second.
回答2:
Why don't you just try to fix your current code as others said? You should not update GUI thousand of times per second, you do realize that this does not make any sense? However, even your example is working without freezing the GUI on my machine (and my machine is old).
Here is an example code how to speed things up.
procedure TWriterThread.write;
var
fileLines, memoLines: TStringList;
i: integer;
filename: string;
procedure InternalSynchronize;
begin
Synchronize(
procedure
begin
Form1.Memo1.Lines.BeginUpdate;
Form1.Memo1.Lines.AddStrings(memoLines);
SendMessage(Form1.Memo1.Handle, EM_LINESCROLL, 0, Form1.Memo1.Lines.Count); //used to scroll down the memo
Form1.Memo1.Lines.EndUpdate;
end
);
end;
begin
fileLines := TStringList.Create;
memoLines := TStringList.Create;
filename := generateFilename;
try
for I := 1 to Self.linesToPrint do
begin
if i mod 50 = 0 then //Update GUI one per 50 writes.
InternalSynchronize;
memoLines.Clear;
memoLines.Add('Writing: ' + IntToStr(I) + ' to ' + generateFilename);
fileLines.Add(Format('Line number: %d', [i]));
end;
InternalSynchronize;
fileLines.SaveToFile(filename);
finally
fileLines.Free;
memoLines.Clear;
end;
end;
BTW: if Memo1
is flickering during update, you might consider to use DoubleBuffered property on a form.
回答3:
Personally I think you should "limit max FPS" as they do in 3D games.
I think you do not try to show user some fluent shapes smoothly transforming one into another, instead you want him to see some textual information.
Then you have to give user time to see and read the text. If you do not - then there is no point to even show it.
So I think you should
Set
TTimer
on the form that would update few times per second. In my experience with simple text like "12,5% of the job done" or "123 cells of 4321 processed" the frequency is to be between 2 and 4 re-renderings per second (TTimer
delay between 250 and 500 msec ). The more complex your output is - the more time it would take for the human being to read it - the less should be the frequency.The threads should emit the data-to-be-rendered into a special multi-writers/single-reader buffer. For the simple texts like I outlined above Windows Messages are well enough (just don't forget your threads should use
PostMessage
neverSendMessage
). For more complex data I'd suggest you to useiOmniBlockingCollection
fromOmniThreads Library
- http://otl.17slon.comYou
TTimer
event should check for availability of the new data, render it as fast as it can - usingTStrings.BeginUpdate
atTMemo.Lines
REALLY matters - and remove from the buffer.When process is over, you stop the
TTimer
and issue out of order data/form update.
That way you cap the FPS to remove flickering and that both lets user see anything and frees CPU of meaningless work.
See also my answers to
- Delphi 7 Occasional deadlock changing TLabel.Font.Style from IdHTTPListener event
- NetShareEnum not displaying shares correctly
来源:https://stackoverflow.com/questions/34803149/thread-update-gui