Delphi __ Kompendium __ Roz8.pdf

(750 KB) Pobierz
Delphi :: Kompendium :: Roz...
Delphi :: Kompendium :: Rozdział 8 - 4programmers.net
http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_8
Logowanie | Rejestracja | Forum | Pomoc | Reklama | Szukaj
Strona główna :: Delphi :: Kompendium
Rozdział 8
Edytuj Historia
Delphi
Artykuły
Kompendium
Gotowce
FAQ
.NET
Turbo Pascal
FAQ
PHP
FAQ
Java
FAQ
C/C++
Artykuły
FAQ
C#
Wprowadzenie
Assembler
FAQ
(X)HTML
CSS
JavaScript
Z pogranicza
Algorytmy
WIĘCEJ »
Aplikacje wielowątkowe
Słowo wątek może mieć różne znaczenie. W świecie programistów może oznaczać możliwość wykonywania
wielu czynności naraz. Przykładowo w systemie Windows możemy uruchamiać kilka programów działających
jednocześnie ? każdy program jest osobnym wątkiem. W tym rozdziale zajmiemy się tworzeniem kilku wątków
w ramach jednego procesu.
Spis treści
1 Czym tak naprawdę są wątki?
2 Klasa TThread
3 Deklaracja klasy TThread
4 Tworzenie nowej klasy wątku
5 Kilka instancji wątku
5.1 Tworzenie klasy
5.2 Kod klasy
6 Wznawianie i wstrzymywanie wątków
7 Priorytet wątku
8 Synchronizacja
8.1 Treść komentarza
9 Zdarzenia klasy TThread
10 Przykład: wyszukiwanie wielowątkowe
10.1 Jak to działa?
10.2 Wyszukiwanie
10.3 Obliczanie czasu przeszukiwania
10.4 Kod źródłowy modułu
11 Podsumowanie
Delphi
C/C++
Turbo Pascal
Assembler
PHP
Programy
Dokumentacja
Kursy
Komponenty
WIĘCEJ »
Procesem można nazwać każdą aplikację, uruchomioną w danym momencie. Taką też
terminologię będę stosował w dalszej części tego rozdziału. Zatem przyjmijmy, że proces to
egzemplarz aplikacji uruchomiony w systemie.
Czym tak naprawdę są wątki?
Zacznijmy od wyjaśnienia, czym tak naprawdę są wątki. Każda aplikacja ( proces ) działająca w systemie
Windows posiada tzw. wątek główny (ang. primary thread ), który może uruchamiać inne wątki poboczne (ang.
secondary threads ). W tym samym czasie może działać kilka wątków pobocznych, które wykonują różne lub te
same operacje. Spójrz na rysunek 8.1. Program przedstawiony na tym rysunku dokonuje wyszukiwania
wielowątkowego, analizując jednocześnie wszystkie dyski znajdujące się w systemie.
Rysunek 8.1. Wyszukiwanie wielowątkowe
W tym wypadku zadaniem każdego wątku jest wyszukanie plików na osobnym dysku. W rezultacie jeden
wątek przypada na każdy dysk, dzięki czemu wyszukiwanie trwa naprawdę szybko.
Pełny kod źródłowy programu Wyszukiwanie wielowątkowe możesz znaleźć na płycie CD-ROM w katalogu
../listingi/8/Wyszukiwarka.
Być może to, co napisałem do tej pory przybliżyło Ci trochę zasadę funkcjonowania wątków. Wyobraź sobie
możliwość wykonywania innych czynności w tle aplikacji ? bez jej jednoczesnego blokowania. Dajesz
użytkownikowi możliwość dokonywania zmian w programie, a w tle może działać inny wątek, który wykonywać
będzie pozostałe operacje.
1 z 10
2009-03-14 15:37
77980996.010.png 77980996.011.png 77980996.012.png 77980996.013.png 77980996.001.png 77980996.002.png
Delphi :: Kompendium :: Rozdział 8 - 4programmers.net
http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_8
Klasa TThread
RSS | Forum | Pastebin |
Regulamin | Pomoc | Usuń
cookies | Prawa autorskie |
Kontakt | Reklama
Podczas tworzenia aplikacji wielowątkowych będziemy korzystali z klasy VCL ? TThread . Istnieje oczywiście
możliwość tworzenia wątków przy wykorzystaniu mechanizmów WinAPI, lecz klasa TThread w dużym stopniu
zwalnia nas z mozolnego kodowania ? jest po prostu łatwiejsza w obsłudze.
Klasa TThread znajduje się w module Classes.pas .
Copyright © 2000-2006 by Coyote Group 0.9.3-pre3
Czas generowania strony: 1.3930 sek. (zapytań SQL:
12)
Deklaracja klasy TThread
Deklaracja klasy TThread znajduje się w pliku Classes.pas i przedstawia się w następujący sposób:
TThread = class
private
FHandle: THandle;
FThreadID: THandle;
FTerminated: Boolean ;
FSuspended: Boolean ;
FFreeOnTerminate: Boolean ;
FFinished: Boolean ;
FReturnValue: Integer ;
FOnTerminate: TNotifyEvent;
FMethod: TThreadMethod;
FSynchronizeException: TObject ;
procedure CallOnTerminate;
function GetPriority: TThreadPriority;
procedure SetPriority ( Value: TThreadPriority ) ;
procedure SetSuspended ( Value: Boolean ) ;
protected
procedure DoTerminate; virtual ;
procedure Execute; virtual ; abstract ;
procedure Synchronize ( Method: TThreadMethod ) ;
property ReturnValue: Integer read FReturnValue write FReturnValue;
property Terminated: Boolean read FTerminated;
public
constructor Create ( CreateSuspended: Boolean ) ;
destructor Destroy; override ;
procedure Resume;
procedure Suspend;
procedure Terminate;
function WaitFor: LongWord ;
property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
property Handle: THandle read FHandle;
property Priority: TThreadPriority read GetPriority write SetPriority;
property Suspended: Boolean read FSuspended write SetSuspended;
property ThreadID: THandle read FThreadID;
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
end ;
Działanie wątku można wstrzymać lub wznowić dzięki metodom Suspend i Resume . Rozpoczęcie wątku jest
jednak realizowane za pomocą metody Execute .
Tworzenie nowej klasy wątku
Jeżeli chcemy utworzyć nowy wątek, jedynym rozwiązaniem jest zadeklarowanie w kodzie programu nowej
klasy, dziedziczącej po TThread. Klasę tę możemy samodzielnie wpisać bezpośrednio w kod programu lub
skorzystać z kreatora Delphi.
Z menu File wybierz New/Other , co spowoduje otwarcie Repozytorium (o Repozytorium pisałem w rozdziale
4.). Wystarczy na zakładce New wybrać pozycję Thread Object (rysunek 8.2).
Rysunek 8.2. Okno Repozytorium
Po naciśnięciu przycisku OK zostaniesz poproszony o wpisanie nazwy klasy w odpowiednim oknie. Wpisz np.
TMojWatek . Wówczas stworzony zostanie nowy moduł, a w nim deklaracja nowej klasy (patrz listing 8.1).
Listing 8.1. Kod źródłowy nowego modułu wygenerowanego przez Delphi
unit Unit2;
interface
uses
Classes;
2 z 10
2009-03-14 15:37
77980996.003.png 77980996.004.png 77980996.005.png 77980996.006.png
Delphi :: Kompendium :: Rozdział 8 - 4programmers.net
http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_8
type
TMojWatek = class ( TThread )
private
{ Private declarations }
protected
procedure Execute; override ;
end ;
implementation
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure TMojWatek.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ TMojWatek }
procedure TMojWatek. Execute ;
begin
{ Place thread code here }
end ;
end .
Nowy moduł zawiera klasę TMojWatek , w której umieszczona jest jedna metoda (w sekcji protected ). To właśnie
w metodzie Execute należy umieścić właściwy kod wątku. Ponadto w module znajduje się ciekawy komentarz,
który zostanie przeze mnie omówiony w dalszej części rozdziału.
W każdym bądź razie nie jest konieczne tworzenie nowego modułu dla klasy wątku. Nie jest także konieczne
tworzenie samej klasy w taki sposób, w jaki to przedstawiłem. Równie dobrze można zadeklarować klasę
samodzielnie.
Podczas samodzielnego deklarowania klasy dziedziczącej po TThread nie wolno zapominać o deklaracji metody
Execute . Metoda Execute musi być umieszczona w sekcji protected i opatrzona dyrektywą override .
Kilka instancji wątku
W każdej klasie wątku mogą być oczywiście deklarowane metody i właściwości ? zupełnie tak samo, jakby to
była zwykła klasa. Istnieje także możliwość uruchamiania kilku klas wątku jednocześnie! Powoduje to
stworzenie dla każdej klasy osobnej instancji zmiennej i zarezerwowanie osobnego bloku pamięci.
Tworzenie wątku przedstawia się następująco:
TMojWatek. Create ( False ) ;
Po wywołaniu konstruktora klasy uruchamiany jest cały proces (metoda Execute ), a to za sprawą parametru
typu Boolean zawartego w konstruktorze. Jeżeli wartość tego parametru to True , uruchomienie wątku nastąpi
dopiero po wywołaniu metody Resume .
Nie zaleca się uruchamiania w tym samym czasie dużej ilości wątków w ramach tego
samego procesu. Zalecana ilość to 16 wątków w ramach jednego procesu.
Tworzenie klasy
Przedstawię Ci teraz przykładowy program tworzący trzy wątki pochodne, które będą działać jednocześnie. Ich
działanie nie spowoduje zablokowania programu ? użytkownik będzie mógł przeciągać okno programu,
minimalizować go itp.
Przykładowy program będzie banalny i raczej niepraktyczny. Wątek wylosuje jakąś liczbę z zakresu 0?999 i
wykona pętle for od liczby 1 do tej wylosowanej wartości. Pętla będzie wykonywana tylko przez jakiś czas
?dzięki spowalnianiu (funkcja Sleep ). Przerwa między kolejnymi iteracjami to 100 milisekund. Program
przedstawiony został na rysunku 8.3.
Rysunek 8.3. Działanie trzech wątków naraz
Postęp wykonywania pętli przedstawiony jest za pomocą komponentów TProgressBar .
Kod klasy
Deklaracja klasy jest dość prosta ? wykorzystujemy jedną metodę, konstruktor oraz dwie właściwości:
type
3 z 10
2009-03-14 15:37
77980996.007.png
Delphi :: Kompendium :: Rozdział 8 - 4programmers.net
http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_8
TGoThread = class ( TThread )
private
FV : Integer ; // wylosowana liczba
FCounter : Integer ; // numer wątku
protected
procedure Execute; override ;
public
constructor Create ( Counter : Integer ) ;
end ;
Deklarowanie konstruktora przez programistę nie jest konieczne, lecz ja stworzyłem go ze względu na
konieczność przekazania do klasy pewnego parametru, jakim jest numer wątku:
constructor TGoThread. Create ( Counter: Integer ) ;
begin
inherited Create ( False ) ; // wywołanie wątku
FCounter := Counter; // przypisanie wartości do zmiennej
end ;
Na początku w konstruktorze wywołujemy konstruktor klasy bazowej. Następnie zmiennej (polu) FCounter
przypisujemy wartość, która została podana wraz z parametrem konstruktora.
Oto, jak wygląda główna procedura ? Execute :
procedure TGoThread. Execute ;
var
i : Integer ;
begin
FreeOnTerminate := True ; // zwolnij po zakończeniu wątku
Randomize ;
FV := Random ( 1000 ) ;
{ odnalezienie komponentu na formularzu }
TProgressBar ( MainForm. FindComponent ( 'ProgressBar' + InttoStr ( FCounter ))) . Max := FV;
for i := 0 to FV do
begin
Sleep ( 10 ) ;
TProgressBar ( MainForm. FindComponent ( 'ProgressBar' + IntToStr ( FCounter ))) . Position := i;
end ;
end ;
Zwróć uwagę na przypisanie do właściwości FreeOnTerminate wartości True . Spowoduje to zwolnienie klasy po
zakończeniu działania wątku.
Kolejne instrukcje są już ściśle związane z działaniem owego wątku. Ciekawą konstrukcją jest:
TProgressBar ( MainForm. FindComponent ( 'ProgressBar' + InttoStr ( FCounter ))) . Max := FV;
Taki zapis umożliwia znalezienie na formularzu komponentu bez znajomości jego nazwy. Wystarczy jedynie
podać nazwę komponentu w parametrze funkcji FindComponent . Kompletny kod źródłowy modułu znajduje się
w listingu 8.2.
Listing 8.2. Kod źródłowy modułu
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
TMainForm = class ( TForm )
gbHome: TGroupBox;
ProgressBar1: TProgressBar;
ProgressBar2: TProgressBar;
ProgressBar3: TProgressBar;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
btnGo: TButton;
procedure btnGoClick ( Sender: TObject ) ;
private
{ Private declarations }
public
{ Public declarations }
end ;
TGoThread = class ( TThread )
private
FV : Integer ; // wylosowana liczba
FCounter : Integer ; // numer wątku
protected
procedure Execute; override ;
public
constructor Create ( Counter : Integer ) ;
end ;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm. btnGoClick ( Sender: TObject ) ;
begin
{ utworzenie trzech wątków }
4 z 10
2009-03-14 15:37
77980996.008.png
Delphi :: Kompendium :: Rozdział 8 - 4programmers.net
http://4programmers.net/Delphi/Kompendium/Rozdzia%C5%82_8
TGoThread. Create ( 1 ) ;
TGoThread. Create ( 2 ) ;
TGoThread. Create ( 3 ) ;
end ;
{ TGoThread }
constructor TGoThread. Create ( Counter: Integer ) ;
begin
inherited Create ( False ) ; // wywołanie wątku
FCounter := Counter; // przypisanie wartości do zmiennej
end ;
procedure TGoThread. Execute ;
var
i : Integer ;
begin
FreeOnTerminate := True ; // zwolnij po zakończeniu wątku
Randomize ;
FV := Random ( 1000 ) ;
{ odnalezienie komponentu na formularzu }
TProgressBar ( MainForm. FindComponent ( 'ProgressBar' + InttoStr ( FCounter ))) . Max := FV;
for i := 0 to FV do
begin
Sleep ( 10 ) ;
TProgressBar ( MainForm. FindComponent ( 'ProgressBar' + IntToStr ( FCounter ))) . Position := i;
end ;
end ;
end .
Wznawianie i wstrzymywanie wątków
Klasa TThread posiada metody, dzięki którym możemy wznowić lub zatrzymać wykonywanie danego wątku.
Zadanie wstrzymywania i wznawiania wykonywania danego wątku realizuje metoda Suspend i Resume .
var
MojWatek : TMojWatek;
begin
MojWatek := TMojWatek. Create ( True ) ;
MojWatek. Resume ; // uruchomienie wątku
MojWatek. Suspend ; // wstrzymanie
end ;
O tym, czy wątek jest w danym momencie uruchomiony, informuje właściwość Suspended . Przyjmuje ona
wartość True , jeżeli wątek jest wstrzymany, natomiast w przeciwnym wypadku ? False .
Priorytet wątku
Wątkom można nadawać różne priorytety, zależnie od ?ważności? zadania, jakie dany wątek wykonuje. Nadając
operacji wyższy priorytet uzyskujesz pewność, że procesor przydzieli czas wykonania właśnie naszemu wątkowi.
Priorytet nadaje się wątkom poprzez właściwość Priority , wykorzystując takie oto wartości: tpIdle , tpLowest ,
tpLower , tpNormal , tpHigher , tpHighest , tpTimeCritical . Najniższym priorytetem jest tpIdle ? taki wątek
jest wykonywany wtedy, gdy żaden inny proces nie wymaga użycia procesora (np. wygaszacze ekranu).
Natomiast priorytet tpTimeCritical otrzymują procesy, które wymagają użycia procesora w trybie
natychmiastowym.
MojWatek. Priority := lpHigher; // nadanie wyższego priorytetu
Nie należy zbytnio przesadzać z nadawaniem wątkom priorytetów. Zalecane jest zachowanie
priorytetu normalnego ( tpNormal ). Nadanie wątkowi zbyt wysokiego priorytetu może
spowodować nieprawidłowe działanie pozostałych programów uruchomionych w tym samym
czasie.
Synchronizacja
Należy rozważyć jeszcze jedną sytuację, a mianowicie uruchamianie kilku wątków w tym samym czasie. Jeżeli
owe wątki modyfikują właściwości lub dokonują jakichkolwiek innych zmian w bibliotece VCL, może dojść do
kolizji. Dotyczy to np. przypadku, gdy owe wątki muszą pobierać jakieś wartości z komponentów i jednocześnie
je modyfikować. W tym celu zalecane jest użycie metody Synchronize klasy TThread .
procedure TGoThread. Execute ;
begin
Synchronize ( SetProprties ) ;
end ;
W ten sposób wątek wywołuje metodę Synchronize , w której podana została nazwa procedury do wykonania ?
SetProprties . Dzięki temu masz pewność, że spośród kilku uruchomionych w danym momencie funkcji tylko
jedna będzie wykonywana w danym czasie i tylko ona będzie mogła dokonywać zmian w bibliotece VCL.
Treść komentarza
Na początku rozdziału chcąc stworzyć nowy wątek, użyłeś Repozytorium. W module, który został utworzony
przez Delphi, widniał taki komentarz:
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
5 z 10
2009-03-14 15:37
77980996.009.png
Zgłoś jeśli naruszono regulamin