在壓力測試工具(客戶端/伺服器架構,Socket 通訊)中,高併發呼叫量下頻繁出現 EAccessViolation、EInvalidCast、斷言失敗等錯誤。追蹤發現:伺服器端 OnDisconnect 事件中直接銷毀連線對應的呼叫物件(FLine),但發送資料的呼叫堆疊中後續程式碼仍嘗試存取該物件,導致「用後釋放」錯誤。
// 問題代碼:OnDisconnect 中直接銷毀物件
procedure TServer.ServerClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
...
FLines.DestroyLineBySocket(Socket); // 不當時機釋放物件
...
end;
// 呼叫端代碼片段
FLine.DoSomething;
FLine.SendSocketData; // 觸發 OnDisconnect,物件被銷毀
FLine.DoOtherThings; // 此處存取已釋放物件 → AV/InvalidCast
採用「延遲銷毀」策略:OnDisconnect 將待銷毀物件丟入垃圾池,在訊息迴圈空閒時機(如 Application.OnIdle 或定時器)統一回收。核心要求:線程安全、可控制回收頻率、自動清理。
unit untGarbageCollector;
interface
uses Classes;
type
TGarbageCollector = class(TObject)
private
FList: TThreadList; // 線程安全列表
public
constructor Create;
destructor Destroy; override;
procedure Put(const AObject: TObject); // 丟入垃圾池
procedure Recycle(const MaxCount: Integer); // 批次回收(限制數量)
end;
function GarbageCollector: TGarbageCollector; // 單例存取
implementation
var _GarbageCollector: TGarbageCollector;
function GarbageCollector: TGarbageCollector;
begin
if not Assigned(_GarbageCollector) then
_GarbageCollector := TGarbageCollector.Create;
Result := _GarbageCollector;
end;
constructor TGarbageCollector.Create;
begin
FList := TThreadList.Create;
end;
destructor TGarbageCollector.Destroy;
begin
try
Recycle(FList.LockList.Count); // 程式結束前全數回收
finally
FList.UnlockList;
end;
FList.Free;
inherited;
end;
procedure TGarbageCollector.Put(const AObject: TObject);
begin
FList.LockList.Add(AObject);
FList.UnlockList;
end;
procedure TGarbageCollector.Recycle(const MaxCount: Integer);
var I: Integer; AList: TList;
begin
AList := FList.LockList;
try
I := 0;
while (AList.Count > 0) and (I < MaxCount) do
begin
TObject(AList.Last).Free; // 釋放物件
AList.Delete(AList.Count - 1);
Inc(I);
end;
finally
FList.UnlockList;
end;
end;
initialization
finalization
if Assigned(_GarbageCollector) then _GarbageCollector.Free;
end.
基本用法:
uses untGarbageCollector;
var Obj: TMyObject;
begin
Obj := TMyObject.Create;
GarbageCollector.Put(Obj); // 非立即釋放,延遲回收
end;
// 定時器或 OnIdle 中呼叫回收
GarbageCollector.Recycle(10); // 每次最多回收 10 個物件
應用場景:
這個輕量級框架解決了多執行緒環境下物件生命週期管理的經典難題,代碼簡潔且易於整合進既有專案。