TTimer, blokowanie w głównym watku

0

Witam,
Mam być może banalny problem, a może nie. Otóż, mój program (pomocnik w kompilacji źródeł) wykonuje wiele różnych operacji, czasami wymagających. Na formie wyświetlam całkowity czas działania różnych operacji, który odświeżany jest w czasie rzeczywistym (a program po prostu zbiera informacje z różnych plików tworząc nowe, w pętlach, często zagnieżdżonych, odpala kompilację podprogramów Delphi (w linii komend, przy użyciu MSBuild), odpala zewnętrzny kompilator (NSIS, tworzy instalator) i różne takie bzdety).

Do tak prostej operacji jak wyświetlanie czasu używam standardowych komponentów, TTimer oraz TLabel. Ale, jest tutaj PROBLEM - jeśli program wykonuje jakieś bardziej wymagające operacje (wystarczy pętla po wielu elementach) czas nie odświeża się. Zajęty główny wątek programu uniemożliwia działanie timera.

procedure TMainFrm.TimerTimer(Sender: TObject);
begin
   MainFrm.Lbl_StopTime.Caption := TimeTostr(Now);
end;

Teoretycznie może pomóc instrukcja Application.ProcessMessages. Ale, niestety - to też nie pomaga.

Jakie jest rozwiązanie tego wydawałoby się banalnego problemu? Z pewnością czegoś nie wiem.
Szukając informacji znalazłem ciekawe rozwiązanie z osobnym wątkiem i odpalaniem zdarzenia, coś takiego (powinno działać, ale nie działa!):

type
  TTimerThread = class(TThread)
  private
    FTickEvent: THandle;
    procedure ProcessGUI;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure FinishThreadExecution;
  end;

type
  TMainFrm = class(TForm)

  private
    { Private declarations }
    FTimerThread: TTimerThread;
  public
    { Public declarations }
  end;


//...

procedure TMainFrm.FormCreate(Sender: TObject);
begin
   FTimerThread := TTimerThread.Create(False);
end;

procedure TMainFrm.FormDestroy(Sender: TObject);
begin
   FTimerThread.FinishThreadExecution;
end;

{ TTimerThread }
constructor TTimerThread.Create(CreateSuspended: Boolean);
begin
   inherited;
   FreeOnTerminate := True;
   FTickEvent := CreateEvent(nil, True, False, nil);
end;

destructor TTimerThread.Destroy;
begin
   CloseHandle(FTickEvent);
   inherited;
end;

procedure TTimerThread.FinishThreadExecution;
begin
   Terminate;
   SetEvent(FTickEvent);
end;
procedure TTimerThread.Execute;
begin
   while not Terminated do
      begin
         if WaitForSingleObject(FTickEvent, 100) = WAIT_TIMEOUT then
            begin
               Synchronize(ProcessGUI);
            end;
      end;
end;
procedure TTimerThread.ProcessGUI;
begin
         Application.ProcessMessages;
         MainFrm.Lbl_StopTime.Caption := TimeTostr(Now);
end;

Proszę o jakiś konstruktywny przykład rozwiązania tego problemu.
(Wyświetlenie czasu operacji, podczas np. dodawania 100 000 operacji w pętli, czy cokolwiek zajmującego główny wątek).

-Pawel

2

Jeżeli dalej wykonujesz swoje operacje na plikach w głównym wątku GUI to się nie odświeży bo jest zajęty. Najlepiej przenieś te operacje do innego wątku.

1

Czy sugerujesz przenieść wszystkie operacje do innego wątku, a w głównym pozostawić timer?
Bo w obecnym scenariuszu, gdzie większość działa w głównym wątku, a timer działa w osobnym wątku nie działa to.

3

Główny wątek aplikacji odpowiedzialny jest za przetwarzania kolejki komunikatów i zablokowanie go powoduje zamrożenia apki. W VCL praktycznie wszystko opiera się na WinApi a więc właśnie w dużym stopniu na obsłudze kolejki komunikatów. W głównym wątku aplikacji zrób wątek poboczny na czasochłonne operacje i będzie śmigało. Tylko gdybyś potrzebował z tego pobocznego wątku dobrać się do komponentów VCL to musisz to zrobić przy użyciu jakiejś metody synchronizacji ale to już inna sprawa.

1
Pepe napisał(a):

[…] a timer działa w osobnym wątku nie działa to.

Timer nie jest wątkiem pobocznym – on działa w ramach głównego wątku, dlatego jeśli go zablokujesz operacjami trwającymi np. 10 sekund, to timer przez te 10 sekund będzie również zamrożony. I właśnie dlatego powinieneś wszystko przenieść do wątków pobocznych, a zwykłego timera w ogóle nie używać.

0

Tego się niestety obawiam - zastanawiam się tylko, dlaczego rozwiązanie które pokazałem powyżej nie działa. Przecież odliczanie realizowane jest w osobnym wątku, a kontrolka VCL (TLabel) jest synchronizowana zgodnie ze sztuką...

0

To się obawiaj a nie próbuj w takim razie nie jesteśmy w stanie pomóc.

0

A tutaj jeszcze tylko pokaże, jak to działa (nieprawidłowe odświeżanie czasu). Mam nadzieję, że jak pokończę inne ważniejsze sprawy w tym programie, dodam obsługę wątków (jak się uda) i będzie to śmigać. Jak widać, na początku czas w ogóle jest zablokowany, dopiero po wykonaniu bardziej czasochłonnych działań idzie (bo reszta jest odpalana jako osobne procesy).
Wideo (45Mb): https://www.dropbox.com/s/q5y1tfwinc9m42j/2020-05-16-11-04-27.mp4?dl=0

0

Nie programuję w Delphi, więc nie wiem, czy moją sugestię da się wdrożyć.
Ale w WINAPI są tzw. timery multimedialne:
https://docs.microsoft.com/en-us/windows/win32/multimedia/multimedia-timers
W szczególności zobacz na funkcję timeSetEvent:
https://docs.microsoft.com/en-us/previous-versions//dd757634(v=vs.85)
Taki timer powinien rozwiązać Twój problem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1