第六節: TList 與泛型
TList 是一個重要的容器,用途廣泛,配合泛型,更是如虎添翼。
我們先來改進一下帶泛型的 TList 基類,以便以后使用。
本例源碼下載(delphi XE8版本):
FooList.Zip
unit
uFooList;
interface
uses
Generics
.
Collections;
type
TFooList <T>=
class
(TList<T>)
private
procedure
FreeAllItems;
protected
procedure
FreeItem(Item: T);virtual;
// 子類中需要重載此過程。以確定到底如何釋放 Item
// 如果是 Item 是指針,就用 Dispose(Item);
// 如果是 Item 是TObject ,就用 Item.free;
public
destructor
Destroy;override;
procedure
ClearAllItems;
procedure
Lock;
// 給本類設計一把鎖。
procedure
Unlock;
end
;
// 定義加入到 List 的 Item 都由 List 來釋放。
// 定義釋放規則很重要!只有規則清楚了,才不會亂套。
// 通過這樣簡單的改造, TList 立馬好用 N 倍。
implementation
{ TFooList<T> }
procedure
TFooList<T>.ClearAllItems;
begin
FreeAllItems;
Clear;
end
;
destructor
TFooList<T>.Destroy;
begin
FreeAllItems;
inherited
;
end
;
procedure
TFooList<T>.FreeAllItems;
var
Item: T;
begin
for
Item
in
self
do
FreeItem(Item);
end
;
procedure
TFooList<T>.FreeItem(Item: T);
begin
end
;
procedure
TFooList<T>.Lock;
begin
System
.
TMonitor
.
Enter(self);
end
;
procedure
TFooList<T>.Unlock;
begin
System
.
TMonitor
.
Exit(self);
end
;
end
.
將第五節的例子用 TFooList 改寫:
unit
uFrmMain;
interface
uses
Winapi
.
Windows, Winapi
.
Messages, System
.
SysUtils, System
.
Variants, System
.
Classes, Vcl
.
Graphics,
Vcl
.
Controls, Vcl
.
Forms, Vcl
.
Dialogs, Vcl
.
StdCtrls, uCountThread, uFooList;
type
TCountThreadList =
Class
(TFooList<TCountThread>)
// 定義一個線程 List
protected
procedure
FreeItem(Item: TCountThread); override;
// 指定 Item 的釋放方式。
end
;
TNumList =
Class
(TFooList<
Integer
>);
// 定義一個 Integer List
TFrmMain =
class
(TForm)
memMsg: TMemo;
edtNum: TEdit;
btnWork: TButton;
lblInfo: TLabel;
procedure
FormCreate(Sender: TObject);
procedure
FormDestroy(Sender: TObject);
procedure
btnWorkClick(Sender: TObject);
procedure
FormCloseQuery(Sender: TObject;
var
CanClose:
Boolean
);
private
{ Private declarations }
FNumList: TNumList;
FCountThreadList: TCountThreadList;
FBuff: TStringList;
FBuffIndex:
Integer
;
FBuffMaxIndex:
Integer
;
FWorkedCount:
Integer
;
procedure
DispMsg(AMsg:
string
);
procedure
OnThreadMsg(AMsg:
string
);
function
OnGetNum(Sender: TCountThread):
Boolean
;
procedure
OnCounted(Sender: TCountThread);
procedure
LockCount;
procedure
UnlockCount;
public
{ Public declarations }
end
;
var
FrmMain: TFrmMain;
implementation
{
$R
*.dfm}
{ TFrmMain }
{ TCountThreadList }
procedure
TCountThreadList
.
FreeItem(Item: TCountThread);
begin
inherited
;
Item
.
Free;
end
;
procedure
TFrmMain
.
btnWorkClick(Sender: TObject);
var
s:
string
;
thd: TCountThread;
begin
btnWork
.
Enabled :=
false
;
FWorkedCount :=
0
;
FBuffIndex :=
0
;
FBuffMaxIndex := FNumList
.
Count -
1
;
s :=
'共'
+ IntToStr(FBuffMaxIndex +
1
) +
'個任務,已完成:'
+ IntToStr(FWorkedCount);
lblInfo
.
Caption := s;
for
thd
in
FCountThreadList
do
begin
thd
.
StartThread;
end
;
end
;
procedure
TFrmMain
.
DispMsg(AMsg:
string
);
begin
memMsg
.
Lines
.
Add(AMsg);
end
;
procedure
TFrmMain
.
FormCloseQuery(Sender: TObject;
var
CanClose:
Boolean
);
begin
// 防止計算期間退出
LockCount;
// 請思考,這里為什么要用 LockCount;
CanClose := btnWork
.
Enabled;
if
not
btnWork
.
Enabled
then
DispMsg(
'正在計算,不准退出!'
);
UnlockCount;
end
;
procedure
TFrmMain
.
FormCreate(Sender: TObject);
var
thd: TCountThread;
i:
Integer
;
begin
FCountThreadList := TCountThreadList
.
Create;
// 可以看出用了 List 之后,線程數量指定更加靈活。
// 多個線程在一個 List 中,這個 List 可以理解為線程池。
for
i :=
1
to
3
do
begin
thd := TCountThread
.
Create(
false
);
FCountThreadList
.
Add(thd);
thd
.
OnStatusMsg := self
.
OnThreadMsg;
thd
.
OnGetNum := self
.
OnGetNum;
thd
.
OnCounted := self
.
OnCounted;
thd
.
ThreadName :=
'線程'
+ IntToStr(i);
end
;
FNumList := TNumList
.
Create;
// 構造一組數據用來測試
FNumList
.
Add(
100
);
FNumList
.
Add(
136
);
FNumList
.
Add(
306
);
FNumList
.
Add(
156
);
FNumList
.
Add(
152
);
FNumList
.
Add(
106
);
FNumList
.
Add(
306
);
FNumList
.
Add(
156
);
FNumList
.
Add(
655
);
FNumList
.
Add(
53
);
FNumList
.
Add(
99
);
FNumList
.
Add(
157
);
end
;
procedure
TFrmMain
.
FormDestroy(Sender: TObject);
begin
FNumList
.
Free;
FCountThreadList
.
Free;
end
;
procedure
TFrmMain
.
LockCount;
begin
System
.
TMonitor
.
Enter(btnWork);
end
;
procedure
TFrmMain
.
UnlockCount;
begin
System
.
TMonitor
.
Exit(btnWork);
end
;
procedure
TFrmMain
.
OnCounted(Sender: TCountThread);
var
s:
string
;
begin
LockCount;
// 鎖不同的對象,宜用不同的鎖。
// 每把鎖的功能要單一,鎖的粒度要最小化。才能提高效率。
s := Sender
.
ThreadName +
':'
+ IntToStr(Sender
.
Num) +
'累加和為:'
;
s := s + IntToStr(Sender
.
Total);
OnThreadMsg(s);
inc(FWorkedCount);
s :=
'共'
+ IntToStr(FBuffMaxIndex +
1
) +
'個任務,已完成:'
+ IntToStr(FWorkedCount);
TThread
.
Synchronize(
nil
,
procedure
begin
lblInfo
.
Caption := s;
end
);
if
FWorkedCount >= FBuffMaxIndex +
1
then
begin
TThread
.
Synchronize(
nil
,
procedure
begin
DispMsg(
'已計算完成'
);
btnWork
.
Enabled :=
true
;
// 恢復按鈕狀態。
end
);
end
;
UnlockCount;
end
;
function
TFrmMain
.
OnGetNum(Sender: TCountThread):
Boolean
;
begin
// 將多個線程訪問 FNumList 排隊。
FNumList
.
Lock;
try
if
FBuffIndex > FBuffMaxIndex
then
begin
result :=
false
;
end
else
begin
Sender
.
Num := FNumList[FBuffIndex];
result :=
true
;
inc(FBuffIndex);
end
;
finally
FNumList
.
Unlock;
end
;
end
;
procedure
TFrmMain
.
OnThreadMsg(AMsg:
string
);
begin
TThread
.
Synchronize(
nil
,
procedure
begin
DispMsg(AMsg);
end
);
end
;
end
.
通過這五節學習,相信大家已能掌握 delphi 線程的用法了。
下一節課程內容,我們將利用 TFooList 設計更高級實用的線程工具。也是本線程教程的完結篇。