使用 Indy TIdTCPServer 時,在 OnConnect 事件 (執行於**工作線程**) 中直接調用 Form2.Show,程序瞬間凍結——經典的 **VCL 非線程安全** 死鎖。
TIdTCPServer 每連接一個線程 (TIdPeerThread)。Form.Show 觸發窗口創建、消息泵、繪製,若在工作線程調用,會與主線程消息泵爭用鎖 → 死鎖。procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
Form2.Show; { 錯誤!工作線程直接操作 VCL }
end;
procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
Timer1.Enabled := True; { Timer 事件在主線程觸發 }
end;
procedure TServerForm.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
Form2.Show;
end;
缺點:依賴定時器輪詢,延遲不可控;高併發下多次觸發邏輯複雜。
procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
AThread.Synchronize(
procedure
begin
Form2.Show; { 此處自動切回主線程執行 }
end
);
end;
或傳統匿名方法不支援的舊版本:
type
TServerForm = class(TForm)
procedure DoShowForm2;
end;
procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
AThread.Synchronize(DoShowForm2);
end;
procedure TServerForm.DoShowForm2;
begin
Form2.Show;
end;
TIdPeerThread.Synchronize 內部調用 TThread.Synchronize,將傳入的方法封裝為 TThreadProcedure 推送到主線程隊列。Application.ProcessMessages / Application.Run) 定期執行隊列中的同步方法。procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
var
PeerIP: string;
begin
PeerIP := AThread.Connection.Socket.Binding.PeerIP;
AThread.Synchronize(
procedure
begin
LogMemo.Lines.Add('Client connected: ' + PeerIP);
if not Form2.Visible then Form2.Show;
end
);
end;
| 錯誤做法 | 後果 | 正確做法 |
|---|---|---|
工作線程直接 Form.Create/Show/Close | 死鎖/崩潰/界面異常 | Synchronize 切回主線程 |
| 工作線程直接訪問 UI 控件屬性 | 數據競態/繫統錯誤 | Synchronize 讀寫,或用線程安全隊列 + 主線程定時器消費 |
Synchronize 內耗時操作 (睡眠/IO) | 阻塞主線程界面卡頓 | 僅做 UI 更新,耗時任務留在工作線程 |
忘記 AThread.Connection.IOHandler 線程安全 | 讀寫衝突 | Indy IOHandler 本身線程安全,但業務邏輯需自行同步 |
TIdNotify:異步通知,**不阻塞**工作線程,適合「發後即忘」日誌、進度條更新。Synchronize:同步阻塞,適合**必須等待 UI 完成**的模態對話框、關鍵狀態同步。type
TNotifyShowForm = class(TIdNotify)
protected
procedure DoNotify; override;
public
PeerIP: string;
end;
procedure TNotifyShowForm.DoNotify;
begin
LogMemo.Lines.Add('Async: ' + PeerIP);
end;
procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
var N: TNotifyShowForm;
begin
N := TNotifyShowForm.Create;
N.PeerIP := AThread.Connection.Socket.Binding.PeerIP;
N.Notify; { 非阻塞 }
end;
**鐵律**:Indy 服務端事件 (OnConnect/OnExecute/OnDisconnect) 均在**工作線程**;任何 VCL 操作**必須**通過 Synchronize / TIdNotify 切回主線程。AThread.Synchronize(匿名方法) 是現代 Delphi (D2009+) 最簡潔、可讀、安全的標準寫法。