第三節:設計一個有生命力的工作線程
創建一個線程,用完即扔。相信很多初學者都曾這樣使用過。
頻繁創建釋放線程,會浪費大量資源的,不科學。
1.如何讓多線程能多次被復用?
關鍵是不讓代碼退出 Execute 這個函數,一旦退出此函數,此線程的生命周期即結束。
要做到這一點,就需要在 Execute 中寫一個”死循環“。大致如下:
procedure
TFooThread
.
Execute;
begin
// 0.掛起
while
not
Terminated
do
// Terminated 是 TThread 的一個 Boolean 屬性。
begin
// 1.獲得參數
// 2.計算
// 3.返回結果
// 4.掛起
end
;
end
;
|
原本 TThread 是有掛起功能這個函數的,叫 suspend,但是在 XE2 后,已經廢止此函數。
故需要找一個替代品 TEvent ,此類在 System.SyncObjs 單元中。於是:
unit
uFooThread;
interface
uses
System
.
Classes, System
.
SyncObjs;
type
TFooThread =
class
(TThread)
private
FEvent: TEvent;
// 此類用來實現線程掛起功能
protected
procedure
Execute; override;
public
constructor
Create(CreateSuspended:
Boolean
);
destructor
Destroy; override;
procedure
StartThread;
// 設計線程的啟動函數。
end
;
implementation
constructor
TFooThread
.
Create(CreateSuspended:
Boolean
);
begin
inherited
;
FEvent := TEvent
.
Create(
nil
,
true
,
false
,
''
);
// 默認讓 FEvent 無信號
FreeOnTerminate :=
true
;
// True的意義是,代碼退出 Execute 后,本類自動釋放。
end
;
destructor
TFooThread
.
Destroy;
begin
FEvent
.
Free;
inherited
;
end
;
procedure
TFooThread
.
Execute;
begin
FEvent
.
WaitFor;
// 如果 FEvent 無信號,就一直等。
// 如果 FEvent 已被設置有信號,就退出 WaitFor ,解除阻塞。
// 這樣,就實現了線程的掛起功能。
// 掛起是指線程時空的代碼,停在了當前位置,就是 FEvent.WaitFor 這個位置。
FEvent
.
ResetEvent;
// 清除信號,以便下一次繼續掛起。
while
not
Terminated
do
begin
// 1.獲得參數
// 2.計算
// 3.返回結果
FEvent
.
WaitFor;
// 同上
FEvent
.
ResetEvent;
end
;
end
;
procedure
TFooThread
.
StartThread;
begin
FEvent
.
SetEvent;
// 所謂啟動線程功能,就是要讓 FEvent 有信號,讓它解除阻塞。
end
;
end
.
以上代碼已實現一個有生命力的線程。
2. 如何正常退出線程?
必須正視這個問題,線程代碼必須要有正常的退出方式,切不可用 KillThread 等暴力方法。
// 線程正常退出示例
var
foo: TFooThread;
begin
foo := TFooThread
.
Create(
false
);
// false 是指創建后不掛起,直接運行 Execute 中的代碼。
sleep(
1000
);
// 技術性代碼,請忽略,但此處又不可少。
foo
.
Terminate;
// 此句的功能是 Terminated:=True;
// Terminated TThread 的一個 Boolean 屬性
// 在 Execute 函數中我們用它做為退出循環的標志
// 請學習系統源碼中的英語命名的方法,注意詞性,時態。
// Terminate 是動詞,是一個函數。而 Terminated 是過去分詞,是一個屬性。
foo
.
StartThread;
// 啟動線程。
// FreeOnTerminated 已在 Create 函數中設置為 True 。
// 所以,代碼退出 Execute 后,foo 會自動 free 的。
end
;
3.線程復用示例
unit
uFooThread; // 用於計算的線程類
interface
uses
System
.
Classes, System
.
SyncObjs;
type
TFooThread =
class
;
TOnWorked =
procedure
(Sender: TFooThread)
of
object
;
TFooThread =
class
(TThread)
private
FEvent: TEvent;
protected
procedure
Execute; override;
public
constructor
Create(CreateSuspended:
Boolean
);
destructor
Destroy; override;
procedure
StartThread;
public
Num:
integer
;
Total:
integer
;
OnWorked: TOnWorked;
end
;
implementation
constructor
TFooThread
.
Create(CreateSuspended:
Boolean
);
begin
inherited
;
FEvent := TEvent
.
Create(
nil
,
true
,
false
,
''
);
FreeOnTerminate :=
true
;
end
;
destructor
TFooThread
.
Destroy;
begin
FEvent
.
Free;
inherited
;
end
;
procedure
TFooThread
.
Execute;
var
i:
integer
;
begin
FEvent
.
WaitFor;
FEvent
.
ResetEvent;
while
not
Terminated
do
begin
Total :=
0
;
if
Num >
0
then
begin
for
i :=
1
to
Num
do
begin
Total := Total + i;
sleep(
10
);
//故意讓線程耗時,以達到更好的演示效果。
end
;
end
;
if
Assigned(OnWorked)
then
OnWorked(self);
FEvent
.
WaitFor;
FEvent
.
ResetEvent;
end
;
end
;
procedure
TFooThread
.
StartThread;
begin
FEvent
.
SetEvent;
end
;
end
.
unit
Unit11; //在窗口中調用
interface
uses
Winapi
.
Windows, Winapi
.
Messages, System
.
SysUtils, System
.
Variants, System
.
Classes, Graphics,
Vcl
.
Controls, Vcl
.
Forms, Vcl
.
Dialogs, Vcl
.
StdCtrls, uFooThread;
type
TForm11 =
class
(TForm)
Memo1: TMemo;
Button1: TButton;
Edit1: TEdit;
procedure
FormCreate(Sender: TObject);
procedure
Button1Click(Sender: TObject);
procedure
FormDestroy(Sender: TObject);
private
{ Private declarations }
FooThread: TFooThread;
procedure
OnWorked(Sender: TFooThread);
// 用來接收線程的 OnWorked 事件。
// 取名是任意的,只要參數相同。
// 如果你也可以取名為 procedure OnFininshed (O:TFooThread); 同樣有效。
public
{ Public declarations }
end
;
var
Form11: TForm11;
implementation
{
$R
*.dfm}
// 本例為了照顧初學者,未對控件取正確的名字。
// 以后的章節中,將全部采用合理的命名,且提供源碼下載地址。
procedure
TForm11
.
Button1Click(Sender: TObject);
var
n:
integer
;
begin
Button1
.
Enabled :=
false
;
// 禁用此 button ,以防線程運行期間誤點
// 這是很重要的!用了線程,就要對所有的情況負責。
// button 被禁用后,在線程計算完成的事件中,將恢復
// 就可以繼續點擊它了。
n := StrToIntDef(Edit1
.
Text,
0
);
FooThread
.
Num := n;
FooThread
.
StartThread;
end
;
procedure
TForm11
.
FormCreate(Sender: TObject);
begin
FooThread := TFooThread
.
Create(
false
);
FooThread
.
OnWorked := self
.
OnWorked;
// 如果按另一個定義的名字也可以寫:
// FooThread.OnWorked:=self.OnFininished;
end
;
procedure
TForm11
.
FormDestroy(Sender: TObject);
begin
FooThread
.
Terminate;
FooThread
.
StartThread;
// 釋放線程。此處不是很嚴謹,以后章節的代碼中將完善它。
end
;
procedure
TForm11
.
OnWorked(Sender: TFooThread);
var
s:
string
;
begin
s := IntToStr(Sender
.
Num);
s := s +
'的累加和為:'
;
s := s + IntToStr(Sender
.
Total);
// 此處是線程時空,操作 UI 要用 Synchronize;
TThread
.
Synchronize(
nil
,
procedure
begin
Memo1
.
Lines
.
Add(s); //匿名函數,可以用外部的變量(s)。這是很高級的特性。
Button1
.
Enabled :=
true
;
//恢復按鈕,以便繼續使用。
end
);
// 此處還可以作為繼續啟動線程的入口,下一章節會講解。
end
;
end
.
至此,一個完整的可復用的線程已基本完成。下一節講解,如何將此線程設計得更為通用和嚴謹。