Нишката не се изпълнява в отворена немодална форма

По-долу е част от кода за формуляр за напредък.
Освен ProgressBars (премахнат от кода) има TLabel (LblDots), на който искам да променя надписа (броят на точките нараства).
Във FormShow/FormClose TDotterThread се създава и унищожава.

Проблем:
Виждам, че процедурата Synchronize(DoUpdate), която актуализира етикета, се извиква само когато програмата не върши тежка работа.

Това е формата за напредък:

unit FrmBusy;

interface

uses
   System.SyncObjs, Windows, Messages, SysUtils, System.Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

type
   TUpdateEvent = procedure of object;    // 'of object' to prevent 'Incompatible types: regular procedure and method pointer'

type
   TDotterThread = class(TThread)         // Thread to update LblDots
   private
      FTick: TEvent;
      FUpdater: TUpdateEvent;
   protected
      procedure Execute; override;
      procedure DoUpdate;
   public
      constructor Create;
      destructor Destroy; override;
      property Updater: TUpdateEvent read FUpdater write FUpdater;
      procedure Stop;
   end;

type
  TFormBusy = class(TForm)
    LblDots: TLabel;
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    FShowDots: Boolean;
    FDotterThread: TDotterThread;
    procedure UpdateDots;
  public
    property ShowDots: Boolean write FShowDots;
  end;

implementation

{$R *.DFM}

procedure TFormBusy.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   if FShowDots then FDotterThread.Stop; // Calls Terminate and is FreeOnTerminate
end;

procedure TFormBezig.UpdateDots;
var s: String;
begin
   s := LblDots.Caption;
   if Length(s) = 50 then s := '' else s := s + '.';
   LblDots.Caption := s;
   Application.ProcessMessages;
end;

procedure TFormBusy.FormShow(Sender: TObject);
begin
   LblDots.Caption := '';
   if FShowDots then
   begin
      FDotterThread := TDotterThread.Create;
      FDotterThread.Updater := Self.UpdateDots;
      FDotterThread.Start;
   end;
   BringWindowToTop(Self.Handle);
end;

{ TDotterThread }

constructor TDotterThread.Create;
begin
  FTick := TEvent.Create(nil, True, False, '');
  FreeOnTerminate := true;
  inherited Create(true);  // Suspended
end;

destructor TDotterThread.Destroy;
begin
  FTick.Free;
  inherited;
end;

procedure TDotterThread.DoUpdate;
begin
   if Assigned(FUpdater) then FUpdater;
end;

procedure TDotterThread.Execute;
begin
  while not Terminated do
  begin
     FTick.WaitFor(1000);
     Synchronize(DoUpdate);
  end;
end;

procedure TDotterThread.Stop;
begin
   Terminate;
   FTick.SetEvent;
end;

end.

Формата се извиква и създава така:

procedure TFrmTest.FormCreate(Sender: TObject);
begin
  FFormBusy := TFormBusy.Create(nil);
end;

procedure TFrmTest.FormDestroy(Sender: TObject);
begin
   FFormBusy.Free;
end;

procedure TFrmTest.BtnCompareClick(Sender: TObject);
begin
   FrmTest.FFormBusy.ShowDots := true;
   FrmTest.FFormBusy.Show;
   FrmTest.FFormBusy.Update label/progress bar
   DoHeavyWork1();
   FrmTest.FFormBusy.Update label/progress bar
   DoHeavyWork2();
   etc.
end;      

Какво правя грешно?
TIA


person Jan Doggen    schedule 08.05.2013    source източник
comment
тъй като DoUpdate трябва да се нарича синхронизиран, вие сте в контекста на основната нишка и вашата процедура за актуализиране може да бъде обработена само ако основната нишка има време на престой. Може би можете да го промените, за да обработва DoHeavyWorkx в нишка.   -  person bummi    schedule 08.05.2013
comment
@bummi е правилен - като синхронизирате метода, вие просто го връщате обратно към основната нишка и проваляте цялата цел. Мисля, че важната процедура, която трябва да ни покажете и тази, която трябва да промените, е FFormBusy.UpdateDots - можете ли да публикувате този код? (покажи още точки... още точки!)   -  person J...    schedule 08.05.2013
comment
@J Виждам, че случайно съм изрязал това от кода - редактирах въпроса си, за да го поправя.   -  person Jan Doggen    schedule 08.05.2013


Отговори (1)


Както знаете, целият UI код трябва да се изпълнява в основната GUI нишка. Ето защо се обаждате на Synchronize, за да актуализирате GUI. Синхронизирането работи като цяло по следния начин:

  1. Задачата, която трябва да бъде изпълнена в главната нишка, се поставя в опашка.
  2. Основната нишка се сигнализира, за да покаже, че задачите за синхронизиране са чакащи.
  3. Фоновата нишка блокира.
  4. Когато основната нишка следва да провери дали има чакащи задачи за синхронизиране, тя ги изпълнява.
  5. Фоновата нишка се сигнализира, за да покаже, че задачата е изпълнена.
  6. Фоновата нишка спира да блокира и продължава да се изпълнява.

Това е доста сложен малък танц.

Вашият проблем е, че основната ви нишка е заета с изпълнението на дълго изпълняваща се задача. Вероятно в обажданията до DoHeavyWork1 и DoHeavyWork2. И това означава, че нишката на GUI не изпълнява елемент 4 своевременно. Нещо повече, основната нишка блокира фоновата нишка, което донякъде отрича полезността на нишките.

Вашият проблем, по същество, е, че вашата основна GUI нишка е заета да прави нещо различно от обслужването на GUI. Трябва да посветите вашата GUI нишка на обслужване на GUI. Не трябва да поема нищо друго и със сигурност не никакви дългосрочни задачи. След като успеете да прехвърлите всички задачи, които не са свързани с GUI, от нишката на GUI към фоновите нишки, ще откриете, че приложението ви реагира.

И накрая, препоръчвам ви да премахнете това обаждане до Application.ProcessMessages от UpdateDots. Вероятно сте го добавили, за да се опитате да се справите с вашия GUI, който не реагира. Но това изобщо няма да помогне, защото вашият проблем е, че UpdateDots не се изпълнява навреме.

person David Heffernan    schedule 08.05.2013
comment
Може би за да го обобщя накратко - той трябва да поставя DoHeavyWork в TThread, вместо да се опитва да маршалира UI актуализации от работна нишка. От време на време извикването на Synchronize за актуализиране на потребителския интерфейс от DoHeavyWork, работещо на TThread, би свършило работа. - person J...; 08.05.2013
comment
@J... Подреждането на актуализации на потребителския интерфейс от работна нишка е добре, стига нишката на GUI да реагира и е достъпна за обслужването им. - person David Heffernan; 08.05.2013
comment
Естествено. Виждам как това не се чете точно сега. Исках да противопоставя цялостното разтоварване на задачата на потребителския интерфейс (и само работата на потребителския интерфейс) с работна нишка, само за да бъде върнато отново, срещу разтоварване на тежката работа. - person J...; 08.05.2013
comment
Както знаете, наистина, само това беше потънало в дъното на мозъка ми ;-) Благодаря, bummi също. - person Jan Doggen; 08.05.2013