Wątki
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
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).
Jedna z ciekawszych rzeczy potrzebnych do zaawansowanego programowania...:+}
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 :-).