Thread update GUI

夙愿已清 提交于 2021-02-08 02:03:28

问题


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

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

  2. 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 never SendMessage). For more complex data I'd suggest you to use iOmniBlockingCollection from OmniThreads Library - http://otl.17slon.com

  3. You TTimer event should check for availability of the new data, render it as fast as it can - using TStrings.BeginUpdate at TMemo.Lines REALLY matters - and remove from the buffer.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!