第二節:在線程時空中操作界面(UI)
1.為什么要用 TThread ?
TThread 基於操作系統的線程函數封裝,
隱藏了諸多繁瑣的細節。
適合於大部分情況多線程任務的實現。這個理由足夠了吧?
什么?你要用 windows 的線程 api 來實現多線程?
我可以負責任地告訴你,如果你用 api 來實現多線程任務,
加之你天資聰明,對 delphi 的面向對象思想掌握得非常快,
那么最終也你也會寫一個與 TThread 類似的東西來提高開發效率。
何必折騰呢?
要相信 delphi 的工程師,人家早已看透了一切。咳咳。
同理,要相信微軟的工程師,windows 操作系統是沒有啥大問題的。
更同理,要相信設計手機的工程師,不需要貼膜,人家好不容易把才手機變薄的。
哈哈,扯遠了。。。
(本教程默認操作系統為 windows 7/10 , delphi 的版本為 XE8,大多數代碼均能在 XE2 上運行)
2.線程時空中操作界面(UI)到底有什么門道?
很多教程中都一再強調,線程時空里,不准直接去更新 UI ,但似乎沒有說明原因。
我們假設UI 界面允許多個線程同時去更新,看看會發生什么情況。
如果兩個線程,同時都在界面相同的區域進行畫圖操作,比如一個要畫綠色,一個要紅色,
那么最終,界面上是不是可能出現一個大花臉?
可以這樣朴實地理解,就知道為什么 UI 不允許多線程去操作了。
不是不能,是不得已。
(線程中不允許直接操作 UI,在安卓下同樣適用)
3. TThread.Synchronize() 原理。
是用 SendMessage 函數,發了一個 WM_NULL 消息給窗口。
窗口接到消息后再去更新界面。窗口消息響應事件可以理解為主線程時空。
以下是接上節的實例,來看如何正確地顯示計算結果在窗口上。
unit
Unit10;
interface
uses
Winapi
.
Windows, Winapi
.
Messages, System
.
SysUtils, System
.
Variants, System
.
Classes,
Graphics,
Vcl
.
Controls, Vcl
.
Forms, Vcl
.
Dialogs, uAccumulation, Vcl
.
StdCtrls;
type
TForm10 =
class
(TForm)
Edit1: TEdit;
Button1: TButton;
procedure
Button1Click(Sender: TObject);
private
procedure
OnAccumulated(Sender: TAccumulationThread);
end
;
implementation
{
$R
*.dfm}
procedure
TForm10
.
Button1Click(Sender: TObject);
var
accThread: TAccumulationThread;
begin
accThread := TAccumulationThread
.
Create(
true
);
accThread
.
OnAccumulated := self
.
OnAccumulated;
//指定事件。
accThread
.
FreeOnTerminate :=
true
;
// 線程結束后自動釋放
accThread
.
Num :=
100
;
accThread
.
Start;
end
;
procedure
TForm10
.
OnAccumulated(Sender: TAccumulationThread);
begin
// 這里是線程時空
// 要更新 UI ,要用 Synchorinize 把更新的操作
// 塞到主線程時空里去運行。注意理解:“塞!”
TThread
.
Synchronize(
nil
,
procedure
begin
// 這里的代碼被塞到主線程時空里去了。
Edit1
.
Text := inttostr(Sender
.
Total);
end
);
// Synchronize 第一個參數是 nil
// 第二個參數是一個匿名函數 什么是匿名函數? 以后會介紹到。
end
;
end
.
|
unit
uAccumulation;
interface
uses
Classes;
type
TAccumulationThread =
class
;
//此為提前申明
TOnAccumulated =
procedure
(Sender: TAccumulationThread)
of
object
;
// 如果不提前申明,Sender 就要定義成 TObject
// 在事件函數中,要操作 Sender 就需要強制轉換
TAccumulationThread =
class
(TThread)
protected
procedure
Execute; override;
public
Num:
integer
;
Total:
integer
;
OnAccumulated: TOnAccumulated;
end
;
implementation
procedure
TAccumulationThread
.
Execute;
var
i:
integer
;
begin
Total :=
0
;
if
Num >
0
then
begin
for
i :=
1
to
Num
do
Total := Total + i
end
;
// 當計算完成后,就調用 OnAccumulated 通知調用者
if
Assigned(OnAccumulated)
then
OnAccumulated(self);
end
;
end
.
4. 哪些代碼運行在線程時空?
Execute 函數中運行的、調用的代碼,都是”線程代碼“。與代碼書寫位置無關!!!
Sysnchronize 是個特殊的存在,它可以在線程時空里,把代碼塞到主線程時空里去運行。
第三節,將實現線程如何保持生命力,創建后可以反復使用。慢慢進入實用階段了,請不要錯過。