Zmienna typu TThread - jak sprawdzić czy wątek jeszcze istnieje?

0

Mam kilka miejsc w programie gdzie do zmiennej przypisuje wątek który w momencie przypisywania jest tworzony i uruchamiany z zdeklarowanym FreeOnTerminate na True. I czasami się zdarza jak wyłączam program że wątek trzeba zabić, używam do tego takiego kodu:

  if GetThreadTimes(fThread.Handle, lpCreationTime, lpExitTime, lpKarnelTime, lpUserTime) then
  begin
    fThread.Terminate;
    { Jeśli czas na zamkniecie wątku minął zabij go gwałtownie }
    If WaitForSingleObject(fThread.Handle, 1000) = WAIT_TIMEOUT then
      TerminateThread(fThread.Handle, 0);
  end;

I to działa ale zdarza się że dostane AV ponieważ wątek sam zakończył działanie i zwolnił pamięć a tutaj w pierwszej linii kodu pobieramy handle obiektu który już nie istnieje. Myślę jak to rozwiązać. Najprościej dać to w try except ale to tylko maskowanie problemu. Tą zmienna potrzebuje tylko do zabicia wątku i może zamiast do zmiennej przypisywać cały obiekt przypisać tylko Handle?

1

możesz się np. podpiąć pod zdarzenie OnTerminate wątku i nilować zmienną fThread

1

Niektórzy twierdzą że nie trzeba się do końca martwić uruchomionymi wątkami w trakcie zamykania aplikacji. System operacyjny teoretycznie powinien po tobie posprzątać.
Ja jednak uważam że większym zmartwieniem niż wyciek pamięci jest to co robisz w takim wątku. Ja np. w wątkach staram się tylko czytać a nie zmieniać czy tworzyć jakieś dane. Nawet jak już muszę np. utworzyć jakiś rekord bo wątek tak zdecydował to raczej robię to w ten sposób że wątek powiadamia główny proces że jest taka konieczność i już on się tym zajmuje. W ten sposób zabezpieczam się przed przerwaniem np. tworzenia rekordu. Wiem, wiem, w systemach transakcyjnych i tak wszystko dobrze powinno się skończyć ale nie wszystko robi się transakcjami.

Co do twojego problemu, może twórz listę uruchomionych wątków i w ten sposób na koniec po prostu przeleć pętlą po tych niezakończonych aby je pozabijać?

2
robertz68 napisał(a):

Niektórzy twierdzą że nie trzeba się do końca martwić uruchomionymi wątkami w trakcie zamykania aplikacji. System operacyjny teoretycznie powinien po tobie posprzątać.

nie, system nie ubije wątku i nie posprząta. Zrób test - stwórz wątek, który nigdy się nie kończy, uruchom go i potem zamknij aplikację. Uruchom menadżera zadań i zobacz, że aplikacja cały czas wisi w procesach. I będzie tak wisieć aż do restartu albo ręcznego ubicia procesu.

Ja jednak uważam że większym zmartwieniem niż wyciek pamięci jest to co robisz w takim wątku. Ja np. w wątkach staram się tylko czytać a nie zmieniać czy tworzyć jakieś dane. Nawet jak już muszę np. utworzyć jakiś rekord bo wątek tak zdecydował to raczej robię to w ten sposób że wątek powiadamia główny proces że jest taka konieczność i już on się tym zajmuje. W ten sposób zabezpieczam się przed przerwaniem np. tworzenia rekordu. Wiem, wiem, w systemach transakcyjnych i tak wszystko dobrze powinno się skończyć ale nie wszystko robi się transakcjami.

to jest jakaś bzdura - po to coś się robi w wątku, żeby nie wołać co chwilę głównego wątku. Idealnie to powinno wyglądać tak:

  1. stworzenie wątku i przekazanie mu wszystkich potrzebnych mu danych
  2. uruchomienie go (i wykonanie całego liczenia czy co tam wątek robi)
  3. zakończenie wątku i zwrócenie wyniku

idealnie, pkt2 nigdy nie woła głównego wątku. Tylko, że idealne sytuacje się nie zdarzają więc tych odwołań powinno być jak najmniej i w miarę możliwości nieblokujące (np. używanie PostMessage do wysłania jakiejś informacji do głównego wątku) wątku bocznego jeśli akurat wątek główny jest zajęty.

Co do twojego problemu, może twórz listę uruchomionych wątków i w ten sposób na koniec po prostu przeleć pętlą po tych niezakończonych aby je pozabijać?

ale jaką listę? Przecież pytacz ma uchwyt do wątku, tylko że jeśli wątek skończy się wcześniej to się sam zwalnia (tak, wątek tak potrafi) a programista zostaje ze wskaźnikiem na nieistniejący już obiekt.

1
abrakadaber napisał(a):

nie, system nie ubije wątku i nie posprząta. Zrób test - stwórz wątek, który nigdy się nie kończy, uruchom go i potem zamknij aplikację. Uruchom menadżera zadań i zobacz, że aplikacja cały czas wisi w procesach. I będzie tak wisieć aż do restartu albo ręcznego ubicia procesu.

wiesz co, jestem totalnym amatorem i zazwyczaj zgadzam się z tezami tutejszych użytkowników, szczególnie takich jak ty, z dużym doświadczeniem, ale czy jesteś pewien tego co napisałeś?

Co do następnej części w zasadzie zgadzam się z tobą i w idealnym świecie tak właśnie należy stosować wątki. Jednak bardzo często używane są one do pobierania czegoś z Internetu, właśnie po to aby w trakcie tych operacji nie blokować aplikacji. Niestety w tym momencie dochodzi dodatkowy czynnik który może zakłócić działanie aplikacji, jak stabilność łącza, niedostępny host z danymi źródłowymi, problemy sprzętowe itp. itd. No i właśnie wtedy zaczynają się problemy w stylu (napisze najbardziej prozaiczny przykład), otwierasz plik do zapisu który zamkniesz na koniec procedury z wątku ale to się nie może odbyć bo łącze zostało zerwane - wiem to prostackie.
Wiem że na wszystko można się przygotować i zabezpieczyć ale weź pod uwagę że na tym forum większość użytkowników jest podobnych do mnie a ja np. nie potrafię wszystkiego przewidzieć, dlatego wolę np. aby aplikacja sprawdzała czy są jakieś dane do pobrania w tle ale już po ich pojawieniu się wyświetlała jakiś progress i nawet blokowała interfejs niż miałbym polegać w 100% na moim amatorskim kodzie.
Zauważ że nawet najnowszy Outlook pobierając dane z kont Google (robi to w końcu w wątkach) potrafi co jakiś czas się przyblokować i tylko zabicie procesu ratuje sprawę. Czyli jednak nie jest to takie proste.

Podsumowując, i tak lubię czytać twoje komentarze :).

1

IMO problemem nie jest to jak zatłuc wątek, a sam fakt takiej konieczności.

Rafał D napisał(a):

I czasami się zdarza jak wyłączam program że wątek trzeba zabić, używam do tego takiego kodu:

No dobrze, ale jak w takim razie wygląda jego Execute, że wywołanie Terminate go nie zatrzymuje?

Sam wolałbym poprawić Execute, tak aby zatrzymywanie wątku nie stanowiło problemu, a podczas jego zatrzymywania, po prostu wywołał Terminate i WaitFor (coby wątek na pewno się zatrzymał, zanim wykonane zostaną kolejne instrukcje).

Tą zmienna potrzebuje tylko do zabicia wątku i może zamiast do zmiennej przypisywać cały obiekt przypisać tylko Handle?

Skoro w tym momencie wątek poboczny nie jest już potrzebny, to zamiast wołać mu Terminate i sprawdzać czy zakończył działanie, możesz go od razu ubić za pomocą TerminateThread. No ale mało informacji podałeś, więc…

2

Albo właściwośc Terminated albo mając uchwyt (handle) po WinApi
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodethread wraz całą potrzebną w "nowych" (od XP) systemach otoczką.

3
robertz68 napisał(a):
abrakadaber napisał(a):

nie, system nie ubije wątku i nie posprząta. Zrób test - stwórz wątek, który nigdy się nie kończy, uruchom go i potem zamknij aplikację. Uruchom menadżera zadań i zobacz, że aplikacja cały czas wisi w procesach. I będzie tak wisieć aż do restartu albo ręcznego ubicia procesu.

wiesz co, jestem totalnym amatorem i zazwyczaj zgadzam się z tezami tutejszych użytkowników, szczególnie takich jak ty, z dużym doświadczeniem, ale czy jesteś pewien tego co napisałeś?

Co do następnej części w zasadzie zgadzam się z tobą i w idealnym świecie tak właśnie należy stosować wątki.

Idealnym świecie?
To na cholerę obecne CPU posiadają tyle rdzeni?
Po to aby Twoja aplikacja byłą przyspawana do jednego z nich?

Jednak bardzo często używane są one do pobierania czegoś z Internetu, właśnie po to aby w trakcie tych operacji nie blokować aplikacji.

Niestety w tym momencie dochodzi dodatkowy czynnik który może zakłócić działanie aplikacji, jak stabilność łącza, niedostępny host z danymi źródłowymi, problemy sprzętowe itp. itd. No i właśnie wtedy zaczynają się problemy w stylu (napisze najbardziej prozaiczny przykład), otwierasz plik do zapisu który zamkniesz na koniec procedury z wątku ale to się nie może odbyć bo łącze zostało zerwane - wiem to prostackie.

Nie.
Wtedy wychodzi na światło dzienne nieznajomość materii wątków.

Wiem że na wszystko można się przygotować i zabezpieczyć ale weź pod uwagę że na tym forum większość użytkowników jest podobnych do mnie a ja np. nie potrafię wszystkiego przewidzieć, dlatego wolę np. aby aplikacja sprawdzała czy są jakieś dane do pobrania w tle ale już po ich pojawieniu się wyświetlała jakiś progress i nawet blokowała interfejs niż miałbym polegać w 100% na moim amatorskim kodzie.

Tak, zauważyłem że poziom jest... amatorski i poziom forum również jest taki.
Szkoda, no ale cóż...

Zauważ że nawet najnowszy Outlook pobierając dane z kont Google (robi to w końcu w wątkach) potrafi co jakiś czas się przyblokować i tylko zabicie procesu ratuje sprawę. Czyli jednak nie jest to takie proste.

To jest dowód anegdotyczny panie kolego.
Brzmi dość niepoważnie na profesjonalnym forum.
No chyba że forum jest również anegdotyczne, to nie ma problemu ;-)

Podsumowując, i tak lubię czytać twoje komentarze :).

Hmm...

0
furious programming napisał(a):

IMO problemem nie jest to jak zatłuc wątek, a sam fakt takiej konieczności.

No dobrze, ale jak w takim razie wygląda jego Execute, że wywołanie Terminate go nie zatrzymuje?

Sam wolałbym poprawić Execute, tak aby zatrzymywanie wątku nie stanowiło problemu, a podczas jego zatrzymywania, po prostu wywołał Terminate i WaitFor (coby wątek na pewno się zatrzymał, zanim wykonane zostaną kolejne instrukcje).

Tą zmienna potrzebuje tylko do zabicia wątku i może zamiast do zmiennej przypisywać cały obiekt przypisać tylko Handle?

Skoro w tym momencie wątek poboczny nie jest już potrzebny, to zamiast wołać mu Terminate i sprawdzać czy zakończył działanie, możesz go od razu ubić za pomocą TerminateThread. No ale mało informacji podałeś, więc…

Terminate go nie zatrzymuje ponieważ np. trwa pobieranie jakiś danych z serwera, chwilowy brak połączenia powiedzmy takie czynności które trwają kilkanaście sekund lub dłużej. I teraz jak użytkownik chce przerwać jakąś operację lub program sam zdecyduje że to co chce wykonać nie może zostać teraz wykonane lub trwa za długo ma na celu zakończenie określonych wątków czasami po przez ich gwałtowne zabicie.

Natomiast druga myśl że zamiast wykonać terminate tylko od razu zabić wątek też do końca nie rozwiązuje problemu. Przecież może się zdarzyć że pomiędzy sprawdzeniem czy wątek istnieje i jego zabiciem może on zakończyć swoje działanie i zwolnić pamięć.

2
Rafał D napisał(a):

Terminate go nie zatrzymuje ponieważ np. trwa pobieranie jakiś danych z serwera, chwilowy brak połączenia powiedzmy takie czynności które trwają kilkanaście sekund lub dłużej.

Nie jestem ekspertem od oprogramowania sieciowego, ale istnieje coś takiego jak timeout, który można samemu ustalić (w zależności od tego, czego używasz do pobierania danych). Jeśli czas oczekiwania na pakiet danych wydłuża się za bardzo (osiąga się timeout), to powinieneś liczyć timeouty (krótkie), a po doliczeniu określonej ich liczby, przerwać proces pobierania i ładnie posprzątać (ewentualnie wstrzymać pobieranie i dać użytkownikowi wybór co robić dalej — jak w przeglądarkach).

W ten sposób odciążysz główną pętlę wątku, łatwo będzie przerwać taki wątek (pętla będzie wykonywać dużo krótkotrwałych iteracji i testować Terminated), a w razie jakichkolwiek problemów z łączem (duże lagi lub przerwane połaczenie), będzie można odpowiednio zareagować (czyli wstrzymać pobieranie lub je przerwać i zakończyć działanie wątku). No i nie będzie również problemu z zamykaniem aplikacji.

Natomiast druga myśl że zamiast wykonać terminate tylko od razu zabić wątek też do końca nie rozwiązuje problemu. Przecież może się zdarzyć że pomiędzy sprawdzeniem czy wątek istnieje i jego zabiciem może on zakończyć swoje działanie i zwolnić pamięć.

Owszem, dlatego takiego rozwiązania nie powinieneś brać pod uwagę. Skup się na timeoutach.

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