线程更新GUI

时间:2016-01-15 01:54:34

标签: multithreading delphi

我多年前使用过Delphi,我记得在版本7中,即使那时我也没有太多的知识,从那时起我就用C ++工作,目前我正在使用Delphi XE10在一家公司工作并实现了搜索我发现了许多不同版本的随机内容,这让我有点困惑。

我正在玩线程,我想更新GUI并搜索我发现Synchronize及其工作方式显示了它对性能的影响,您可以注意到应用程序冻结。

我想知道是否有更顺畅的方法来处理这个问题,比如事件,通知或其他什么?

@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.

3 个答案:

答案 0 :(得分:2)

您的代码没有错 - 它只是花了很多时间在很多很小的VCL GUI更新中。

我怀疑Memo重绘不是快速程序(当Memo逐行填充数千个字符串时,注意BeginUpdate/EndUpdate影响。因此尽可能少地进行同步调用(如果需要,发送字符串包)。实际上,用户无法在第二次看到1000次更新。

答案 1 :(得分:2)

为什么不像其他人所说的那样尝试修复当前的代码? 你不应该每秒更新GUI数千次,你是否意识到这没有任何意义?但是,即使您的示例工作也没有冻结我的机器上的GUI(我的机器已经老了)。

以下是如何加快速度的示例代码。

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:如果Memo1在更新期间闪烁,您可能会考虑在表单上使用DoubleBuffered属性。

答案 2 :(得分:0)

我个人认为你应该像3D游戏那样“限制最大FPS”。

我认为你不会试图向用户展示一些流畅的形状,将一个流畅地转换为另一个,而是希望他看到一些文字信息。

然后你必须给用户时间来查看和阅读文本。 如果你不这样做 - 那就没有意义甚至显示它。

所以我认为你应该

  1. 在表单上设置TTimer,每秒更新几次。根据我对简单文本的经验,如“完成工作的12.5%”或“处理4321的123个单元”,频率为每秒2到4次重新渲染(TTimer延迟250到500之间毫秒)。你的输出越复杂 - 人类阅读它所需的时间越多 - 频率就越低。

  2. 线程应将要渲染的数据发送到特殊的多写入器/单读取器缓冲区中。对于像我上面概述的简单文本,Windows消息已经足够了(只是不要忘记你的线程应该使用PostMessage从不SendMessage)。对于更复杂的数据,我建议您使用iOmniBlockingCollection中的OmniThreads Library - http://otl.17slon.com

  3. TTimer事件应该检查​​新数据的可用性,尽可能快地呈现 - 使用TStrings.BeginUpdate TMemo.Lines真正重要 - 并从缓冲区中删除

  4. 当流程结束时,您停止TTimer并发出无序数据/表单更新。

  5. 这样你可以限制FPS以消除闪烁,并且两者都可以让用户看到任何东西并释放CPU无意义的工作。

    另见我对

    的回答