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.
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...:+}