首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi IdTCPServer 死鎖問題:在 OnConnect 事件中顯示窗體的正確姿勢

     編輯:Delphi

現象還原

使用 Indy TIdTCPServer 時,在 OnConnect 事件 (執行於**工作線程**) 中直接調用 Form2.Show,程序瞬間凍結——經典的 **VCL 非線程安全** 死鎖。

根因分析

  • TIdTCPServer 每連接一個線程 (TIdPeerThread)。
  • VCL 控件**只能在主線程**創建/顯示/操作。
  • Form.Show 觸發窗口創建、消息泵、繪製,若在工作線程調用,會與主線程消息泵爭用鎖 → 死鎖。

錯誤代碼

procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
  Form2.Show;  { 錯誤!工作線程直接操作 VCL }
end;

方案一:Timer 延遲到主線程 (不推薦,有競態)

procedure TServerForm.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
  Timer1.Enabled := True;  { Timer 事件在主線程觸發 }
end;

procedure TServerForm.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  Form2.Show;
end;

缺點:依賴定時器輪詢,延遲不可控;高併發下多次觸發邏輯複雜。

方案二:TThread.Synchronize / AThread.Synchronize (標準正解)

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 vs Synchronize

  • 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+) 最簡潔、可讀、安全的標準寫法。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved