Program wielowątkowy - zarządzanie wątkami

0

Witam. Często używałem wątków, gdy musiałem wykonać jakieś operacje, ale nie chciałem by aplikacja się w tym czasie zawieszała.

Jednak teraz stanąłem przed dylematem. Moja aplikacja musi teraz pracować w kilku lub kilkunastu wątkach. Muszę mieć "podgląd" np. w stringlist jakie aktualnie wątki są uruchomione i mieć możliwość ich wyłączenia, a więc zarządzania. Tylko jak to teraz sensownie rozwiązać by było możliwie prosto do napisania i działało tak jak trzeba? Czy jest jakaś gotowa opcja do wyświetlenia listy aktywnych wątków i całego zarządzania?

A może jest jakaś inna lepsza opcja niżeli wątki, by program podczas pracy się nie przywieszał?

A może jakiś komponent lub inne rozwiązanie do wygodniejszego zarządzania wątkami?

0

Czy jest jakaś gotowa opcja do wyświetlenia listy aktywnych wątków i całego zarządzania?

Coś w rodzaju:

Var ThreadList: TList<TMyThread>; // zmienna globalna lub lokalna w kontekście unitu
{...}
Constructor TMyThread.Create;
Begin
 ThreadList.Add(self);
End;

Destructor TMyThread.Free;
Begin
 ThreadList.Remove(self);
End;

Wydaje się najłatwiejszym i najrozsądniejszym rozwiązaniem, przyjmując, że nie tworzysz wątków w wątkach (wtedy odwoływanie się do ThreadList byłoby niebezpieczne :P).

0

O, to jest wspaniałe. Do tej pory opierałem się na zmiennych globalnych boolean w głównej klasie, ale było to niewygodne, awaryjne i nieprofesjonalne. Teraz będzie lepiej dzięki temu rozwiązaniu którego nie znałem ;)

Jeżeli ktoś ma jeszcze jakieś porady to proszę bardzo.

Niestety rozwiązuje to problem tylko listy. Co jeszcze z zamykaniem danego wątku z listy?

0

ThreadList[id_watku].Terminate;? (destruktor sam go usunie z listy)

0

Dzięki ;)

0

Nie bardzo mogę sobie z tym poradzić. Nie ma sensu mojego kodu umieszczać bo próbowałem wielu wersji, i za każdym razem ogólne błędy o braku dostępu.

Był byś tak miły i podał przykład kodu z przykładową klasą? Czyli klasa, button który tworzy klasa, no i te destruktory i konstruktory. Do tej pory używałem tylko jednej Execute i tak jest w każdym poradniku o wątkach, i dlatego się wyłożyłem.

Proszę o pomoc

0

Z takich prostszych przykładów, takie coś wpadło mi na myśl:

Wygląd zewnętrzny:
gui.png

Kod:

implementation

{$R *.lfm}

Type TMyThread = Class(TThread)
                  Public
                   Var ID     : UInt32; // każdy wątek ma swój unikalny identyfikator
                       Counter: UInt64; // pole, które w wątku jest inkrementowane (zwiększane) co milisekundę

                   Constructor Create(const CreateSuspended: Boolean); // konstruktor

                  Protected
                   Procedure Execute; override;
                  End;

Type TThreadList = TFPGList<TMyThread>; // Lazarusowy odpowiednik `TList` z Delphi (czy jak oni tam to nazwali; chodzi o generyczną listę/tablicę/cokolwiek)
Var ThreadList: TThreadList;
    LastID    : UInt32 = 0; // ID ostatniego wątku; na jej podstawie pobierane jest ID nowego.

// findThreadByID | szuka wątku o podanym `ID`
Function findThreadByID(const ID: UInt32): TMyThread;
Begin
 For Result in ThreadList Do
  if (Result.ID = ID) Then
   Exit;

 Exit(nil);
End;

(* TMainForm.UpdateTimerTimer *)
procedure TMainForm.UpdateTimerTimer(Sender: TObject);
Var Thread : TMyThread;
    LastSel: Int32;
begin
 lbValues.Items.BeginUpdate;
 LastSel := lbValues.ItemIndex;

 lbValues.Clear;
 For Thread in ThreadList Do
  lbValues.Items.Add(IntToStr(Thread.ID)+': '+IntToStr(Thread.Counter)); // wyświetlamy dane w tabeli (listboxie) w formacie: `ID wątku: wartość`

 lbValues.ItemIndex := LastSel;
 lbValues.Items.EndUpdate;
end;

(* TMainForm.btnNewThreadClick *)
procedure TMainForm.btnNewThreadClick(Sender: TObject);
begin
 TMyThread.Create(False); // nie musimy nigdzie tego zapisywać, ponieważ sam konstruktor wątku już doda go do listy aktualnie działających (do `ThreadList`)
end;

(* TMainForm.btnRemoveSelectedClick *)
procedure TMainForm.btnRemoveSelectedClick(Sender: TObject);
Var Str: String;
begin
 if (lbValues.ItemIndex <> -1) Then // jeżeli cokolwiek zaznaczone...
 Begin
  Str := lbValues.Items[lbValues.ItemIndex];
  FindThreadByID(StrToInt(Copy(Str, 1, Pos(':', Str)-1))).Terminate; // szukamy wątku o podanym ID i zakańczamy jego żywot ( ;( )
  lbValues.ItemIndex := -1;
 End;
end;

(* TMyThread.Create *)
Constructor TMyThread.Create(const CreateSuspended: Boolean);
Begin
 Counter := 0;
 ID      := LastID;

 Inc(LastID);

 inherited Create(CreateSuspended);

 ThreadList.Add(self); // dodajemy nasz wątek do puli aktualnie działających
End;

(* TMyThread.Execute *)
Procedure TMyThread.Execute;
Begin
 Try
  FreeOnTerminate := True;

  While not ((Terminated) or (Application.Terminated)) Do
  Begin
   Inc(Counter);
   Sleep(1);
  End;
 Finally
  ThreadList.Remove(self); // gdy wątek zakończy działanie, usuwamy go z listy
 End;
End;

initialization
 ThreadList := TThreadList.Create;
end.
0

Ratujesz mi życie. Analizuje kod i co do jednej kwestii mam wątpliwości.

Tutaj będę miał jedno execute do każdego nowego wątku, a nie tak jak wcześniej że w każdym execute osobnym miałem wszystkie instrukcje (nie wiem czy rozumiesz).

Czy mogę w tym wypadku przy tworzeniu nowego wątku dodać parametr np. nazwy, i w zależności od niej w execute wywoływać daną funkcję z głównego unitu bez obawy o błędy i zawiechy?

Dla przykładu: Dla testu (jeszcze zanim dostałem Twój kod) położyłem na formę IdHttp. W funkcji Test dałem

IdHttp1.Get('tutaj adres');

Do buttona przypisałem wywoływanie funkcji Test. Jako adres celowo podałem wolną w działaniu stronę, przez co po odpaleniu programu i wciśnięciu przycisku forma zawiesza się na kilka sekund - normalne zachowanie.

Dlatego zrobiłem prosty wątek, i tam w execute dałem:

Form1.Test;

Po odpaleniu wątku nie ma żadnych zawiech, strona pobiera się w tle, tak jak trzeba, mimo że funkcja należy do głównej formy, unitu.

I tutaj wracam do mojego pytania. Czy tak to mogę robić, skoro działa? Czy wątek może być tylko pośrednikiem, łącznikiem pomiędzy formą a funkcjami i procedurami? Czy mogę więc korzystać bezpośrednio z głównych funkcji i procedur, zamiast pisać wszystko w np. Execute i inne w wątku tak jak to robiłem wcześniej?

Mam nadzieję że dość dokładnie to wytłumaczyłem.

0

Czy mogę w tym wypadku przy tworzeniu nowego wątku dodać parametr np. nazwy, i w zależności od niej w execute wywoływać daną funkcję z głównego unitu bez obawy o błędy i zawiechy?

Tak.

Czy tak to mogę robić, skoro działa?

Specem od wątków nie jestem, natomiast jeżeli uruchomisz po kilka wątków i każdy z nich będzie chciał wywołać w tym samym czasie metodę z formy głównej, możesz zcrashować aplikację.
Bezpieczniej będzie mimo wszystko korzystać z poprzedniej metody, czyli:

Procedure TMyThread.Execute;
Begin
 Case ThreadName of
  'page0': WykonajCostam1(); // globalne procedury lub metody klasy `TMyThread`
  'page1': WykonajCostam2();
0

Bardzo dziękuję Ci za pomoc!

user image

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