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
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
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
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
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
Plik z chomika:
Wiewioor
Inne pliki z tego folderu:
Delphi __ Kompendium __ Roz1.pdf
(1299 KB)
Delphi __ Kompendium __ Roz10.pdf
(777 KB)
Delphi __ Kompendium __ Roz11.pdf
(1764 KB)
Delphi __ Kompendium __ Roz12.pdf
(834 KB)
Delphi __ Kompendium __ Roz13.pdf
(1951 KB)
Inne foldery tego chomika:
■ePub
A.B Strugaccy
Ann Rice - Śpiąca królewna
Ćwiek Jakub
Ebooki-EPUB-PL-MegaPack
Zgłoś jeśli
naruszono regulamin