Wątki

Adam Boduch

Co to właściwie są te wątki? Jest to oddzielny kod, który wykonuje jakąś czynność. Wykonuje tę czynność niezależnie od tego co się z aplikacją dzieje ( tzn., że możesz swój formularz przemieszczać, zmieniać rozmiary itp. ). Wyobraź sobie, że program wykonuje sobie jakieś obliczenia niezależnie od tego co robisz w danym programie ( pisanie tekstu, obróbka grafiki ). Wątki właśnie tak działają - "w tle". Przykładem działania wątku jest wpisywanie do komórek arkuszu kalkulacyjnego wartości i obliczenie np. sumy.

Zaraz się zresztą o tym przekonasz.

Delphi udostępnia specjalną klasę - TThread, która umożliwia zastosowanie wielowątkowości.
Utworzenie wątku to wybranie z menu File polecenia New, a następnie pozycje Thread Object.
Delphi wówczas stworzy nowy moduł z nową klasą. Tak, tak, wątek to nie żaden komponent tylko odrębna klasa. Tak więc żeby stworzyć wątek nie wystarczy położyć na formie komponent :) ale trzeba trochę popisać. :) Nowy wygenerowany moduł powinien wyglądać tak:

 TTest = class(TThread)
 protected
   procedure Execute; override;
 end;

Ja zawsze wpisuje to ręcznie do modułu i rzadko korzystam z tej możliwości.

Ja akurat użyłem nazwy TTest. Polecam najpierw poczytanie artykułu "KLASY", aby dowiedzieć się coś więcej o klasach.

Jak więc widzisz utworzona została nowa klasa dziedzicząca z innej - TThread. Klasa ta zawiera nową pozycję w sekcji protected.

Tak więc kod, który znajduje się powyżej wpisz do Twojego programu w sekcji Interface.

Uzupełnij teraz w sekcji Implementation kod procedury Execute:

uses 
  Math;

procedure TTest.Execute;
var
  I : Integer;
begin
  FreeOnTerminate := True; // zakoncz watek po zaknczeniu tej procedury
  for I := 1 to 1000 do
    Power(I, I * 2);
end;

Zastosowanie tej procedury nie ma większego sensu. Podaje ją dla przykładu. Zwróć uwagę na pierwszą linię tej procedury. Powoduje ona zakończenie wątku wraz z zakończeniem tej procedury. Masz już wątek - teraz trzeba go uruchomić:

procedure TForm1.Button1Click(Sender: TObject);
var
  Test : TTest;
begin
  Test := TTest.Create(False);
end;

To powoduje uruchomienie wątku. Jako parametr wywołania tego konstruktora wpisałem FALSE co oznacza, że wątek będzie automatycznie uruchamiany. Jeżeli w tym miejscu wpiszesz True to wątek pozostanie w stanie "spoczynku", a jego wywołanie spowoduje użycie polecenia:

Test.Resume;

Teraz coś trudniejszego - obliczenia ile czasu program jest uruchomiony. Program będzie posiadał jeną etykietę ( lblCount ) oraz dwa przyciski typu TButton ( btnStart, btnStop ). Pierwszy z przycisków będzie służył do rozpoczęcia odliczania, a drugi do zakończenia.

Na początek sama klasa:

 TStoper = class(TThread)
 private
   Count : Integer;
 protected
   procedure Execute; override;
 end;

W sekcji private znajduje się zmienna, która przechowywać będzie ilość sekund :) które upłynęły od czasu naciśnięcia przycisku START. A więc procedura Execute wyglądać będzie tak:

procedure TStoper.Execute;
begin
  FreeOnTerminate := True;
  while not (Application.Terminated) or (Terminated) do
    begin
      Sleep(1000);
      Count := Succ(Count);
      MainForm.lblCount.Caption := Format('Aplikacja uruchomiona: %d sekund.', [Count]);
    end;
end;

Procedura zawiera pętle, która wykonywana będzie dopóki program będzie działał lub dopóki wątek będzie w działaniu. Następnie pauza trwająca jedną sekundę ( Sleep ). Powiększenie licznika ( zmienna Count ) o jeden, a następnie wyświetlenie informacji na formie ( MainForm ).

 TStoper = class(TThread)
 private
   Count : Integer;
 protected
   procedure Execute; override;
 end;

var
  MainForm: TMainForm;
  Stoper : TStoper;

implementation

{$R *.dfm}

procedure TStoper.Execute;
begin
  FreeOnTerminate := True;
  while not (Application.Terminated) or (Terminated) do
  begin
    Sleep(1000);
    Count := Succ(Count);
    with MainForm do
      lblCount.Caption := Format('Aplikacja uruchomiona: %d sekund.',
      [Count]);
  end;
end;

procedure TMainForm.btnStartClick(Sender: TObject);
begin
  Stoper.Resume;
end;

procedure TMainForm.btnStopClick(Sender: TObject);
begin
  Stoper.Suspend;
end;

initialization
  Stoper := TStoper.Create(True);

end.

Jak zapewne się domyśliłeś dwa przyciski uruchamiają procedurę Execute ( Resume ) lub zatrzymuje ( Suspend ). W sekcji "Initialization" wpisane są komendy, które mają być wykonane w czasie gdy program będzie ładowany do pamięci komputera. W sekcji tej jest kod, który powoduje tworzenie nowego wątku. Pętla będzie wykonywana dopóty dopóki aplikacja będzie otwarta lub dopóki wątek będzie uruchomiony.

Priorytet wątku

Istnieje możliwość określenie priorytetu wątku. W zależności od ustawionego priorytetu wątek nabierze innej "ważności" w systemie :) Poniżej przedstawione są dostępne priorytety:

tpIdle ( Jałowy ) - jest to najniższy priorytet . Wątek zostanie wykonany tylko wtedy gdy inne aplikacje o wyższym priorytecie nie będą potrzebowały wykorzystać procesora. Przykładem mogą być wygaszacze ekranu, które posiadają właśnie ten priorytet.
tpNormal ( Normalny ) - jest to domyślny priorytet przydzielany wątkom.
tpHigher ( Wysoki ) - nie należy przydzielać tego priorytetu wątkom, które wykonują jakieś skomplikowane obliczenia gdyż może to sparaliżować pracę systemu. Ten priorytet przydziela się wątkom, które muszą otrzymać czas procesora natychmiast.
tpTimeCritical ( Czasu rzeczywistego ) - tego priorytetu używa się bardzo rzadko - służy tylko do wątków, które wykonują krótkie operacje i zaraz się kończą. Użycie tego wątku może się wiązać z paraliżem systemu ( zawieszenie myszki itp. ).
Nadawanie priorytetu nie jest niczym nadzwyczajnym - w powyższym przykładzie powinno to wyglądać tak:

Stoper.Priority := tpNormal;

Delphi udostępnia także odpowiednie wykorzystanie wątków poprzez WinAPI. Nie jest potrzebne wówczas stosowanie klasy Classes. Jest to jednak nieco trudniejsze i na razie nie będę na ten temat tutaj pisał. Możesz o tym poczytać w systemie pomocy WinAPI Delphi pod hasłem 'CreateThread'.

Jak widzisz wątki mogą się przydać jeżeli chcemy, aby jakieś operacje były wykonywane jednocześnie z normalnym wykonywaniem aplikacji.

6 komentarzy

Przydatne jeżeli chemy zatrzymać proces która jest "aktualnie" w trakcie działania, procedurą Break lub Abort wywołaną np. przez kliknięcie przycisku :-).

W "żadko" ort!...

To jak to w końcu powinno być Dryobates biorąc pod uwagę ten przykład z tym synchronize bo mi błędy wyrzuca

Nieźle, ale ja oczywiście muszę troszkę pomarudzić.
Po pierwsze:
with MainForm do
lblCount.Caption := Format('Aplikacja uruchomiona: %d sekund.',
[Count]);
Według pomocy w Delphi do metod i właściwości (czyli także Caption) nie powinno się odwoływać inaczej niż przez Synchronize (inaczej mogą wystąpić konflikty. Np. jeżeli kilka wątków się do tego obiektu odwołuje naraz).
Po drugie: nie wspomniano nic o metodzie Create i jej nadpisywaniu. W praktyce użycie standardowego Create z klasy TThread jest znikome. Zwykle należy przekzać do wątka jakieś parametry (odczytywanie ich z głównego procesu bezpośrednio mija się z celem stosowania wątków. Wątki powinny być jak najbardziej niezależne).
Po trzecie: Sposoby dostępu do zmiennych spoza wątku (nie można użyć var przy konstruktorze, a odwoływanie się w sposób bezpośredni, jak już wspomniałem mija się z celem).

Może dlatego że poźno, ale cos mi nie wychodzi :P

Jedna z ciekawszych rzeczy potrzebnych do zaawansowanego programowania...:+}