Zwalnianie wątków z Sleepem

0

Problem jest taki: gdy damy Sleepa w Execute, to podczas zwalniania trzeba poczekać aż się obudzi.

http://www.delphi3000.com/articles/article_2630.asp

Na podstawie tego art'a zaimplementowałem to do swojej aplikacji, która ma 3 wątki. Jednakże przy zamknięciu jest losowy poślizg (ok. 10s). Czy da się to obejść? Gdy testowałem przy jednym wątku to poślizgu nie ma (przy dwóch to raz na jakąś próbę).

unit ExThread;

interface

uses
  Classes, Windows, Sysutils;

type
  TExThread = class(TThread)
  private
    { Private declarations }
    hEvent: THandle;
    procedure Sleep(Milliseconds:Integer);
  protected
    procedure Execute; override;
  public
    constructor Create(Suspended:Boolean);
    destructor Destroy; override;
  end;

implementation

constructor TExThread.Create(Suspended: BOolean);
var
  lpEventAttributes: PSecurityAttributes;
begin
  inherited;
  FillChar(lpEventAttributes,Sizeof(PSecurityAttributes),0);
  hEvent := CreateEvent(lpEventAttributes,false,False,'EXIT_EVENT');
  if hEvent = 0 then raise Exception.Create('Unable to create EXIT_EVENT');
end;

destructor TExThread.Destroy;
begin
  if not Terminated then Terminate;
  SetEvent(hEvent);
  CloseHandle(hEvent);
  inherited;
end;

procedure TExThread.Execute;
begin
  while not terminated do begin
   { Place thread code here }
    Sleep(15000)
    end;
end;

procedure TExThread.Sleep(Milliseconds:Integer);
begin
  WaitForSingleObject(hEvent,Milliseconds);
end;

end. 
0
while not terminated do 
begin
  { Place thread code here }
  for i := 1 to 150 do
  begin
    if Terminated then
      Exit;
    Sleep(100);
  end;
end;
0

No ostatecznie takie wyjście można zrobić.

A orientujesz się czy ten Event tak "laguje" czy co się może tu dziać ? Bo tak jak pisałem: przy jednym wątku działa to rozwiązanie dobrze, dopiero przy trzech zwalnia :-)

0

Dodaj sobie do wątku jeszcze taką prockę (public):

procedure TExThread.Awake;
begin
  PulseEvent(EventObject); //pobudka śpiochu
end;

Teraz kill twojego wątku wygląda tak:

TwójWątek.Terminate;
TwójWątek.Awake;

Awake możesz wywoływać ile chcesz, bo kiedy wątek śpi to i tak tylko raz go obudzisz.

Jeśli twój wątek nie musi się budzić cyklicznie daj:

WaitForSingleObject(hEvent, INFINITE);

Wtedy będzie spał ile wlezie dopóki nie zrobisz mu Awake.

0

PulseEvent(hEvent) w twoim przypadku, a ten fragment w Destroy powinien być zbędny: if not Terminated then Terminate; SetEvent(hEvent);

0

No dzięki, ten sposób działa dobrze.

Aczkolwiek podczas testów bez twojej modyfikacji, zauważyłem że przez zwalnianie wątku przez Free tak lagowało.
Zmieniłem na FreeOnTerminate i kończenie tylko poprzez Terminate (zamiast Free) i zamyka się aplikacja od razu.

Tylko że gdy wątek nie został uruchomiony, to Terminate powoduje wyciek. Więc który sposób zwalniania zastosować ?

0

Widze że dochodzisz do konstruktywnych wniosków :-) .

Kończ wątki zawsze przez Terminate. Nigdy przez Free chyba że lubisz AV :-)

Zanim zrobisz Terminate i cokolwiek innego na obiekcie (tutaj na wątku) sprawdzaj zawsze (!) czy obiekt istnieje np. tak:

if Assigned(TwójWątek) then
begin
  TwójWątek.Termiante;
  TwójWątek.Awake;
end;

Masz 100% pewności że wszystko pójdzie jak trzeba.
W przypadku formatek jest odrobinę inaczej bo Release nie czyści obiektu i Assigned pokaże że forma istnieje i że możesz na niej zrobić np. Close i dostajesz AV. Rada: dynamiczą formę należy znilować po zrobieniu na niej Release i po problemie. Ten problem jest szczególnie uciążliwy gdy jakąś formatkę tworzysz dynamicznie więcej niż jeden raz. Ale to tylko tak z gwoli ciekawości jak jest w przypadku innego obiektu.

FreeOnTerminate=True ustawiaj w Create wątku, ewentualnie najpóźniej w Execute, ale na samym początku, przed pętlą while. Ja preferuje Create z wygody. Nic się wtedy nie ma prawa chrzanić.

Polecam też nauczyć się obsługi komunikatów SendMessage i PostMessage oraz PostThreadMessage. Odpada ci wtedy użycie Synchronize gdy modyfikujesz VCL z wątków.
A poza tym komunikaty to świetna rzecz, bo prócz wysłania informacji o zdarzeniu, możesz przesyłać np. pomiędzy wątkami albo pomiędzy wątkiem, a główną formą jakieś dane typu, rekord, string, liczba, wskaźnik do obiektu.

0

nie do końca chyba mnie zrozumiałeś.

obiekty wątku zawsze mam utworzone, lecz zauważyłem że jeśli nię będą ani razu zaStartowane (domyślnie mam z Suspend=True), to Terminate nie wywołuje destruktora ?
ReportMemoryLeaksOnShutdown pokazuje że nić nie zostało zwolnione.

Create DataModule

  LsTask := TTask.Create;
  LsClient := TLsClient.Create;
  LsCalc := TLsCalc.Create;

Destroy DataModule

  LsClient.Terminate;
  LsClient.Avake;

  LsCalc.Terminate;
  LsCalc.Avake;

  LsTask.Terminate;
  LsTask.Avake;

W tym wypadku mam wyciek. Gdy wcześniej je wszystkie uruchomię to jest OK.

0

Zrób tak: FreeOnerminate:= True daj w Create wątku na samym początku, a potem na wątku wykonaj Terminate. Na mój rozum powinno zadziałać jak trzeba.
Ewentualnie można też zrobić tak:

if Assigned(TwójWątek) then
begin
  if TwójWątek.Suspended then
    TwójWątek.Resume;
  TwójWątek.Terminate;
  TwojWątek.Awake;
end;
0

No mam w OnCreate FreeTerminate.
Sprawdziłem na czystym projekcie - jest tak jak piszę. Nie ma Startu, to Terminate nie zwalnia.

Po zamknięciu okienka wyskoczy okienko Reportu.

0

U mnie działa bez problemu z takimi zmianami:

destructor thr.Destroy;
begin
  CloseHandle(hEvent);
  inherited Destroy;
end;

oraz:

procedure TForm1.Button1Click(Sender: TObject);
var
  t: thr;
begin
  t := thr.Create;
  t.Resume;
  t.Terminate;
  t.Avake;
end;

Po tych zmianach nie mam już komunikatów o memleakach.

0

no no, mały "czit" z tym resumem :->
No ale dzięki za obszerną pomoc :-)

0

Resume i CloseHandle, ale tego z wcześniejszych postów nie wyłapałeś.

Gdybym ja pisał ten wątek to pozwoliłbym mu się od razu wykonywać (w Create Suspended na False), a FreeOnTerminate = True, żeby od razu zaczął wykonywać to co jest w Execute i tam trafi na WaitForSingleObject i sobie cicho czeka.
Potem co bym nie potrzebował zrobić z wątkiem to zawsze dodaje Awake żeby go wybudzić i żeby wątek wykonał swoją robotę w Execute.
A na koniec jak chce go zabić to robię Terminate i Awake. Resume jest wtedy zbyteczne.

0

Z tym CloseHandlem to było na szybko, zapomniałem do testowego projektu dać.
Co do resume, to faktycznie dam od razu z Suspendem False i bedzie gitara.

0

terminate powinno być przed resume i Awake. Terminate po prostu ustawia właściwość Terminated klasy wątku na True. Jeśli nie uruchamiałeś wątku (cały czas był suspended) to jeśli najpierw zrobisz resume to masz bardzo duże szanse, że pętla główna wątku (to co jest w execute) wykona się przynajmniej raz. Jeśli najpierw ustawisz właściwość Terminated na True (wywołasz Terminate) i w Execute masz tak

procedure TWatek.Execute;
begin
  while not Terminated do
  begin
    //tu coś się robi
  end;
end;

to po obudzeniu wątku po prostu się zakończy

xxl napisał(a)

Kończ wątki zawsze przez Terminate. Nigdy przez Free chyba że lubisz AV :-)
true

Zanim zrobisz Terminate i cokolwiek innego na obiekcie (tutaj na wątku) sprawdzaj zawsze (!) czy obiekt istnieje np. tak:

if Assigned(TwójWątek) then
begin
  TwójWątek.Termiante;
  TwójWątek.Awake;
end;

Masz 100% pewności że wszystko pójdzie jak trzeba.

dla wątku, który się 'sam' zwalnia (ma ustawione FreeOnTermianted) to i tak nie zadziała ponieważ jeśli chociaż raz przypiszesz coś do zmienne TwojWatek to Assigned ZAWSZE zwróci Ci True dopóki sam nie ustawisz zmiennej na nil.

W przypadku formatek jest odrobinę inaczej bo Release nie czyści obiektu i Assigned pokaże że forma istnieje i że możesz na niej zrobić np. Close i dostajesz AV. Rada: dynamiczą formę należy znilować po zrobieniu na niej Release i po problemie.

Co do formatek i obiektów, które się nie zwalniają automatycznie to wystarczy użyć FreeAndNil. BTW nawet w dokumentacji jest napisane, żeby nie używać Release, nigdy.

Ten problem jest szczególnie uciążliwy gdy jakąś formatkę tworzysz dynamicznie więcej niż jeden raz. Ale to tylko tak z gwoli ciekawości jak jest w przypadku innego obiektu.
generalnie problem ten występuje wtedy, kiedy nie do końca programista wie co robi :)

0

Postanowiłem nie zakładać nowego Posta tak aby wszystko było w jednym. Dzięki informacja tutaj zawartym rozwiązałem 90% swoich problemów ale napotkałem na jedną sytuację z którą nie mogę dać sobie rady nawet dziadek Google nie pomógł.

Jak zabić wątek w czasie jego wykonywania, tak aby aplikacja nie czekała na jego zamknięcie tylko natychmiast go zabiła i zwolniła wszystkie zmienne.

Znalazłem procedure TerminateThread(Handle, msec)

Zabija wątek natychmiast ale nie zwalnia zmiennych innych obiektów, które były zainicjowane w np Execute watku. np: T: TStringList;

0

Nie jestem expertem od wątków. Ale wydaje mi się, że jeśłi coś potrzebujesz zwolnić w przypadku klasy TThread poprzez Terminate, to powinieneś ustawić FreeOnTerminate na True, a wszystkie konieczne rzeczy zwolnić w destruktorze tego wątku. Po to według mnie są destruktory, aby prawidłowo pozwalniać obiekty i "posprzątać" po sobie.

0

Panowie, a jak kończycie działający wątek, gdy program jest zamykany? Chodzi mi o to, jak dbacie o to, żeby najpierw zakończył się działający wątek, tzn. pętla "while not Terminated do begin...end" w procedurze Execute wątku wykonała się w całości, a nie została przerwana np.gdzieś w połowie), a dopiero po tym były zwalniane obiekty lub zamykane porty używane w wątku, a na końcu kontynuowane zamykanie programu (niszczenie formy głównej, itd.)?

0

jak masz tak

FreeOnTermiante := True;
while not Terminated do
begin
  //<-tutaj BARDZO DUŻO różnych lub czasochłonnych poleceń
end;

to możesz w środek pętli, np. po każdym poleceniu, które może chwilę trwać wstawić takie coś if Terminated then break; i wtedy warunek wyjścia z pętli i zakończenia wątku jest sprawdzany częściej niż co pełny obieg pętli

0
abrakadaber napisał(a):

jak masz tak

FreeOnTermiante := True;
while not Terminated do
begin
  //<-tutaj BARDZO DUŻO różnych lub czasochłonnych poleceń
end;

to możesz w środek pętli, np. po każdym poleceniu, które może chwilę trwać wstawić takie coś if Terminated then break; i wtedy warunek wyjścia z pętli i zakończenia wątku jest sprawdzany częściej niż co pełny obieg pętli

To jest jakieś rozwiązanie, ale mi chodzi o to, żeby właśnie w pętli while wykonała się cała sekwencja instrukcji (bo z jakichś powodów musi, np. sekwencja modyfikacji w pliku lub w rejestrze), czyli nie było przerywania gdzieś po drodze w przypadku ustawienia Terminated.
A co jeśli w procedurze Execute po pętli while mam jakieś finalizujące instrukcje, które muszą się też wykonać (np. zamykanie portów, plików)?

Mam swoje proste i skuteczne rozwiązanie, ale ciekaw jestem waszych rozwiązań takiego problemu. :)

0
marogo napisał(a):

Mam swoje proste i skuteczne rozwiązanie, ale ciekaw jestem waszych rozwiązań takiego problemu. :)

No jasne my będziemy kombinować a ty nie pokażesz co już zrobiłeś uważaj bo się zdziwisz.

0
marogo napisał(a):
abrakadaber napisał(a):

jak masz tak

FreeOnTermiante := True;
while not Terminated do
begin
  //<-tutaj BARDZO DUŻO różnych lub czasochłonnych poleceń
end;

to możesz w środek pętli, np. po każdym poleceniu, które może chwilę trwać wstawić takie coś if Terminated then break; i wtedy warunek wyjścia z pętli i zakończenia wątku jest sprawdzany częściej niż co pełny obieg pętli

To jest jakieś rozwiązanie, ale mi chodzi o to, żeby właśnie w pętli while wykonała się cała sekwencja instrukcji (bo z jakichś powodów musi, np. sekwencja modyfikacji w pliku lub w rejestrze), czyli nie było przerywania gdzieś po drodze w przypadku ustawienia Terminated.

To dodaj sobie drugą zmienną - Terminated, która kończy natychmiast i np. TermSoft, która czeka na "pełen obieg" pętli i kończy

A co jeśli w procedurze Execute po pętli while mam jakieś finalizujące instrukcje, które muszą się też wykonać (np. zamykanie portów, plików)?
to się robi w destruktorze albo w try finally w zależności od tego czy zmienne są lokalne czy nie

Mam swoje proste i skuteczne rozwiązanie, ale ciekaw jestem waszych rozwiązań takiego problemu. :)
to się może podziel

0

Przed zamknięciem aplikacji możesz poczekać WaitForSingleObject, ale i tak nie może to trwać Bóg wie ile, poza tym zawsze istnieje możliwość, że użytkownik ubije całą aplikację i dane np. będą zapisane niekompletne. Pytanie co chcesz osiągnąć. Jeśli atomowość zapisu to proponuje skorzystać z silnika baz danych i np. transakcji - dane albo zostaną zapisane w całości, albo wcale.

0

No dobra Panowie, napiszę jak ja to robię. Otóż najczęstszą metodą kończenia wątku jest wywołanie Watek.Terminate. Niestety nie ma tu oczekiwania na zakończenie wątku, tylko przestawia się flaga Terminated, co najczęściej wystarczy do wykonania całej procedury Execute i zakończenia wątku, ale tylko podczas pracy programu (w sensie, nie procesu jego zakończenia), bo program ma wystarczający czas na wykonanie wszystkich instrukcji w pętli While (i instrukcji po wyjściu z niej) w procedurze Execute, chyba, że któraś z instrukcji w pętli zawiesi jej zakończenie, ale to już kwestia poprawności tworzenia kodu.
Z tego powodu metoda Terminate nie nadaje się do kończenia wątku w procesie kończenia aplikacji, ponieważ po wywołaniu metody Terminate, program natychmiast przechodzi do dalszych instrukcji kończących aplikację (np zamykanie portów), a wątek nadal jeszcze chwilę działa, próbując wysyłać dane do zamkniętych już portów (tu przy odrobinie "szczęścia" dostajemy AV) i kiedy w końcu system ubije wątek, ten zostanie przerwany na losowej instrukcji pętli While, co wiąże się z niewykonaniem całej sekwencji instrukcji. Takie są moje obserwacje.
Prostym i skutecznym rozwiązaniem tego problemu jest użycie metody Watek.Destroy, która powoduje po pierwsze ustawienie flagi Terminated:=true, po drugie jeśli wątek jest uśpiony wywołanie Resume (co powoduje wykonanie Execute, ale z pominięciem pętli While, gdyż flaga Terminated została przestawiona, czyli najczęściej przeleci Execute nic nie wykonując, chyba że są instrukcje po While), następnie jest kluczowa metoda ** WaitFor
* (odpowiednik WaitForSingleObject dla procesów) oczekująca na zakończenie wątku i na końcu jest zwalnianie wątku z pamięci. Dzięki tej metodzie kończenia wątku mamy pewność, że wątek zakończył się po wykonaniu całej sekwencji instrukcji pętli While oraz dodatkowych instrukcji po tej pętli, jeśli takowe istnieją.
Przykładowy kod może być taki:

Watek.FreeOnTerminate pozostaje domyślnie na false.

 
procedure TForm1.FormDestroy(Sender: TObject);
begin
  If Assigned(Watek) then //gdyby wątek został utworzony
  Begin
    Watek.Destroy;//czekanie na zakończenie wątku (wywołuje Terminate i WaitFor) i zwolnienie wątku z pamięci
    Watek:=nil;
  End;
  ComPort.Close; //tu dalsze instrukcje finalizujące zakończenie aplikacji
  CloseFile(plik);
end;
0

Chciałbym przedstawić pewne rozwiązanie problemu właśnie przy zamykaniu aplikacji gdy wątki działają.

Nasz przykładowy program pobiera pliki graficzne w tle aplikacji (w wątku). Czyli wątek odnosi się do np komponentu FTP z naszej głównej formy.

type TFTP = class(TFTPSend)  //Synapse z dodatkową zmienną informującą o tym czy komponent jest używany;
       private
         BusyFTP: Boolean;
       public
         property Busy: Boolean read BusyFTP;
         constructor Create;
         function Login: Boolean;
         function Logout: Boolean;
       end;

constructor TFTP.Create;
begin
  inherited;
  BusyFTP := False;
end;

function TFTP.Login: Boolean;
begin
  inherited Login;
  if Result then BusyFTP := True;
end;

function TFTP.Logout: Boolean;
begin
  inherited LogOut;
  BusyFTP := False;
end;

Teraz nasz wątek:

type TDownloadPhoto = class(TThread)
       private
         hEvent: THandle;
       public
         constructor Create;
         destructor Destroy; override;
         procedure Awake;
       protected
         procedure Execute; override;
       end;

procedure TDownloadPhoto.Awake;
begin
  PulseEvent(hEvent);
end;

constructor TDownloadPhoto.Create;
begin
  inherited Create(false);
  hEvent := CreateEvent(nil,False,False,nil);
  if hEvent = 0 then raise Exception.Create('Unable to create hEvent in TDownloadPhoto!');
  FreeOnTerminate := True;
end;

destructor TDownloadPhoto.Destroy;
begin
  CloseHandle(hEvent);
  inherited Destroy;
end;


procedure TDownloadPhoto.Execute;
begin
  while not Terminated do
  begin
    WaitForSingleObject(hEvent, INFINITE);
    if Terminated then Exit;
    try
      if not FTP.Login then Continue; // Exit;
      While not WyszukajNowePliki do WaitForSingleObject(hEvent, 60000);   
      if Terminated then Exit;
      PorownajPliki;
      PobierzTylkoNowePliki; 
   finally
      FTP.Logout;
      if not Terminated then Synchronize(KomponentVCL);
    end;
  end;
end;

I na koniec nasza forma główna

procedure TForm1.Create;
begin
  FTP := TFTP.Create
  FTP.Login := 'login';
  (...)
end;

procedure TForm1.Close;
begin
  ThreadPhoto.Terminate;  //Ustawienie flagi
  ThreadPhoto.Awake;      //Wybudzenie i kończenie wątku
  FTP.Free;
end;

I tutaj mamy jeden poważny błąd! Dokładnie w onClose Formy. Ustawiamy flagę i budzimy wątek po czym zwalniamy FTP. Problem jest w tym że gdy wątek wykonuje np funkcję WyszukajNowePliki to korzysta z FTP a my mu tak nagle go zwalniamy dlatego onClose powinno wyglądać tak:

procedure TForm1.Close;
begin
  ThreadPhoto.Terminate;  //Ustawienie flagi
  ThreadPhoto.Awake;      //Wybudzenie i kończenie wątku
  while FTP.Busy do sleep(10);  //Czekamy bo coś używa jeszcze tego 
  {z WaitForSingleObject(ThreadPhoto.hEvent, ...) nie chciało mi to za bardzo działać};  
  FTP.Free;
end;

Warto zwrócić jeszcze na jedną rzecz uwagę otóż

if not Terminated then Synchronize(KomponentVCL);

W momencie gdy klikniemy na zamknięcie formy to komponenty w zasadzie już są zwalniane / pozwalniane? dlatego nie można wykonywać na nich niczego np ustawiania ProgressBar.

Może to się komuś przyda mi na nic lepszego nie udało się wpaść.

0

Można jeszcze wykorzystać

AddTerminateProc();

I wyświetlać jakieś dodatkowe okno z postępem zamykania programu (wątków) i przyciskiem Natychmiastowego zamknięcia aplikacji z informacją o możliwości uszkodzenia np. niezapisanych danych w programie

0

Chciałem przedstawić jeszcze jeden sposób ale proszę o wypowiedź czy takie rozwiązanie jest bezpieczne.

TWątek.Execute 
begin 
  FreeOnTerminate ustawione na true;
end;

w := TWatek.Create;
w.Resume;
(Jakieś operacje)

w.ThreadId / w.Handle <- wątek zakończył już działanie i czy mogę pobrać te zmienne? Teoretycznie nie.

Mogę pobierać te zmienne czy lepiej w czasie kiedy wątek jeszcze działał zapisać / skopiować je gdzieś do zmiennych w programie które istnieją do czasu zamknięcia aplikacji?

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