为什么使用进度条显示迭代的进度会大大增加相关进程的执行时间? 考虑以下示例:
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
for I := 0 to fileSource.Count - 1 do
begin
//Code....
end;
finally
fileSource.Free;
end;
end;
如果您添加进度条的更新:
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
ProgressBar.Properties.Max:= fileSource.Count;
for I := 0 to fileSource.Count - 1 do
begin
Application.ProcessMessages;
ProgressBar.Position:= I;
end;
finally
fileSource.Free;
end;
end;
迭代过程所需的时间极大地增加了。
对200,000行的文件执行读取测试,而不更新进度条,迭代的时间约为8秒,但是,如果激活进度条的更新以显示迭代的进度,这个过程需要几分钟。
测试文件为2,700行,正常时间为2-4秒,但使用进度条,执行时间超过1分钟。
有人可以指出Application ProcessMessages的使用是否不正确?如果例程在单元中或与进度条相同的表单上,结果不会改变。
好的,我可以看到评论,但有人可以通过示例或链接指出在这些条件下更新进度条的正确方法吗?
答案 0 :(得分:0)
你真的没有"只是添加一个进度条"。您使用Application.ProcessMessages;
消息意味着您还在处理其他工作的所有其他消息。所以现在你的忙碌/主要工作"在同一个线程(和CPU)上竞争CPU时间与通过应用程序的所有其他消息竞争。我们当然无法评论您的应用程序可能会传递哪些其他消息。
繁忙的工作不应该在主线程中完成。并且将方法包装到线程中通常相当容易,前提是它已经与GUI过于紧密耦合。
毫无疑问,你已经在评论中告诉了所有这些。
首先要注意一些重要的规则:
然后以下是最低要求:
Execute()
方法实施主要处理。但是,您可以应用许多更高级的注意事项来改进您的线程。 (这些将留待您进一步研究。)
以下示例代码是1-4中所需内容的精简版本。你可以填写我遗漏的那些小部分。
1)
type
TFileProcessor = class(TThread)
public
constructor Create(const AFileName: string);
procedure Execute; override;
end;
2& 4)
请注意,您可以在构造函数中传递进度条实例并从线程更新它。但即使它的工作量稍微多一点,在你的线程上定义一个回调事件也会更加清晰,并允许你的GUI处理事件,以便选择它想要做的事情。
procedure TFileProcessor.Execute;
var
fileSource: TStringList;
I: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
{ GUI interaction must be queued.
ProgressBar.Properties.Max:= fileSource.Count;}
FPosition := 0;
FCount := fileSource.Count;
Queue(DoUpdateProgress);
for I := 0 to fileSource.Count - 1 do
begin
{ Obviously this must go!
Application.ProcessMessages;}
{ Again GUI interaction must be Queued
ProgressBar.Position:= I;}
FPosition := I;
Queue(DoUpdateProgress); {TIP: Reduce your progress updates for
more performance improvement; updating
on every single line is overkill.}
end;
finally
fileSource.Free;
end;
end;
procedure TFileProcessor.DoUpdateProgress();
begin
if Assigned(FOnUpdateProgress) then
FOnUpdateProgress(FPosition, FCount);
end;
3)
procedure TForm1.Button1Click(...);
var
LThread: TFileProcessor;
begin
LThread := TFileProcessor.Create(FFileName);
LThread.OnUpdateProgress := HandleUpdateProgress;
LThread.FreeOnTerminate := True;
LThread.Start;
end;
4)
如前所述,如果您的表单控制它想要更新的GUI控件以及响应进度更新的方式,它会更清晰。例如。如果需要,您可以在同一时间更新标签,而无需更改线程和作业代码。
procedure TForm1.HandleUpdateProgress(APosition, ACount: Integer);
begin
ProgressBar.Position := APosition;
ProgressBar.Properties.Max := ACount;
Label1.Caption := Format('Line %d of %d', [APosition, ACount]);
end;
1 我想强调你应该避免与多线程代码共享数据。跨线程操作比同线程操作昂贵得多。 (这包括对主线程的通知。)
例如,在我的系统上,上面的线程代码有以下开销。
HandleUpdateProgress
中的操作,您可能会发现在文件实际完成处理后,需要一些时间处理所有排队的消息。 (在我的系统上更新标准标签和进度条,这需要5秒钟。)答案 1 :(得分:-1)
如果您的应用程序仅转换输入文件,则不需要特殊线程,您可以使用以下代码。
如果应用程序允许用户在处理文件时执行其他操作,则应使用后台线程。
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I,J: Integer;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
ProgressBar.Properties.Max:= fileSource.Count;
J:=10;//TODO make it better
for I := 0 to fileSource.Count - 1 do
begin
if (I mod J = 0) then
begin
Application.ProcessMessages;
ProgressBar.Position:= I;
end;
end;
ProgressBar.Position:= fileSource.Count;
finally
fileSource.Free;
end;
end;
或者您可以按时调用您的processMessages:
procedure FileToStringList(FileName: String);
var
fileSource: TStringList;
I,J: Integer;
lastCheck: TDateTime;
begin
fileSource:= TStringList.Create;
try
fileSource.LoadFromFile(FileName);
ProgressBar.Properties.Max:= fileSource.Count;
J:=1000;//refresh in ms
lastCheck:=now;
for I := 0 to fileSource.Count - 1 do
begin
if (lastCheck+j)<now then
begin
lastCheck:=now;
Application.ProcessMessages;
ProgressBar.Position:= I;
end;
end;
ProgressBar.Position:= fileSource.Count;
finally
fileSource.Free;
end;
end;