記錄消息只是其中Synchronize()
根本沒有任何意義的那些區域之一。您應該改爲創建一個日誌目標對象,該對象具有受關鍵部分保護的字符串列表,並向其添加日誌消息。讓主VCL線程從該列表中刪除日誌消息,並在日誌窗口中顯示它們。這有幾個優點:
你不需要撥打Synchronize()
,這是一個壞主意。好的副作用是你的關機問題消失。
工作線程可以繼續他們的工作,而不會阻塞主線程事件處理或嘗試記錄消息的其他線程。
由於可以一次將多條消息添加到日誌窗口,因此性能會提高。如果你使用BeginUpdate()
和EndUpdate()
這會加快速度。
我可以看到沒有什麼缺點 - 日誌消息的順序也被保留下來。
編輯:
我會添加一些更多的信息和一些代碼一起玩,爲了說明,有更好的方法做你需要做的事情。
調用Synchronize()
來自與VCL程序中的主應用程序線程不同的線程將導致調用線程阻塞,傳遞的代碼將在VCL線程的上下文中執行,然後調用線程將被解除阻塞並且繼續運行。在單處理器的時代,這可能是一個好主意,無論如何,一次只能運行一個線程,但對於多處理器或內核來說,這是一個巨大浪費,應該不惜一切代價避免。如果在8核心機器上有8個工作線程,那麼將它們調用Synchronize()
可能會將吞吐量限制爲可能的一小部分。
其實,調用Synchronize()
從來不是一個好主意,因爲它會導致死鎖。有一個更有說服力的理由不使用它,永遠。
使用PostMessage()
發送日誌消息會照顧僵局問題的,但它有其自身的問題:
每個日誌字符串將導致公佈和處理的消息,引起了許多開銷。無法一次處理多個日誌消息。
Windows消息只能在參數中攜帶機器字大小的數據。因此發送字符串是不可能的。在字符串轉到PChar
之後發送字符串是不安全的,因爲字符串在處理消息時可能已被釋放。分配工作線程中的內存並在處理完消息後釋放VCL線程中的內存是一種解決方法。一種增加更多開銷的方式。
Windows中的消息隊列具有有限的大小。發佈過多的消息可能會導致隊列變滿並且丟棄消息。這不是一件好事,並且與之前的觀點一起導致內存泄漏。
在生成任何計時器或繪圖消息之前,將處理隊列中的所有消息。源源不斷的大量信息可能會導致程序無響應。
收集日誌信息的數據結構看起來是這樣的:
type
TLogTarget = class(TObject)
private
fCritSect: TCriticalSection;
fMsgs: TStrings;
public
constructor Create;
destructor Destroy; override;
procedure GetLoggedMsgs(AMsgs: TStrings);
procedure LogMessage(const AMsg: string);
end;
constructor TLogTarget.Create;
begin
inherited;
fCritSect := TCriticalSection.Create;
fMsgs := TStringList.Create;
end;
destructor TLogTarget.Destroy;
begin
fMsgs.Free;
fCritSect.Free;
inherited;
end;
procedure TLogTarget.GetLoggedMsgs(AMsgs: TStrings);
begin
if AMsgs <> nil then begin
fCritSect.Enter;
try
AMsgs.Assign(fMsgs);
fMsgs.Clear;
finally
fCritSect.Leave;
end;
end;
end;
procedure TLogTarget.LogMessage(const AMsg: string);
begin
fCritSect.Enter;
try
fMsgs.Add(AMsg);
finally
fCritSect.Leave;
end;
end;
許多線程可以調用LogMessage()
同時,進入臨界區將連續訪問列表,並加入他們的消息後,線程可以繼續他們的工作。
這留下了一個問題:VCL線程如何知道何時調用GetLoggedMsgs()
從對象中移除消息並將它們添加到窗口中。一個窮人的版本將有一個計時器和民意調查。更好的辦法是打電話PostMessage()
添加日誌消息時:
procedure TLogTarget.LogMessage(const AMsg: string);
begin
fCritSect.Enter;
try
fMsgs.Add(AMsg);
PostMessage(fNotificationHandle, WM_USER, 0, 0);
finally
fCritSect.Leave;
end;
end;
這仍然有太多的發佈的消息的問題。一條消息只需在上一條消息被處理時發佈:
procedure TLogTarget.LogMessage(const AMsg: string);
begin
fCritSect.Enter;
try
fMsgs.Add(AMsg);
if InterlockedExchange(fMessagePosted, 1) = 0 then
PostMessage(fNotificationHandle, WM_USER, 0, 0);
finally
fCritSect.Leave;
end;
end;
雖然這仍然可以改進。使用計時器可以解決發佈的消息填滿隊列的問題。下面是一個小的類,它實現這一點:
type
TMainThreadNotification = class(TObject)
private
fNotificationMsg: Cardinal;
fNotificationRequest: integer;
fNotificationWnd: HWND;
fOnNotify: TNotifyEvent;
procedure DoNotify;
procedure NotificationWndMethod(var AMsg: TMessage);
public
constructor Create;
destructor Destroy; override;
procedure RequestNotification;
public
property OnNotify: TNotifyEvent read fOnNotify write fOnNotify;
end;
constructor TMainThreadNotification.Create;
begin
inherited Create;
fNotificationMsg := RegisterWindowMessage('thrd_notification_msg');
fNotificationRequest := -1;
fNotificationWnd := AllocateHWnd(NotificationWndMethod);
end;
destructor TMainThreadNotification.Destroy;
begin
if IsWindow(fNotificationWnd) then
DeallocateHWnd(fNotificationWnd);
inherited Destroy;
end;
procedure TMainThreadNotification.DoNotify;
begin
if Assigned(fOnNotify) then
fOnNotify(Self);
end;
procedure TMainThreadNotification.NotificationWndMethod(var AMsg: TMessage);
begin
if AMsg.Msg = fNotificationMsg then begin
SetTimer(fNotificationWnd, 42, 10, nil);
// set to 0, so no new message will be posted
InterlockedExchange(fNotificationRequest, 0);
DoNotify;
AMsg.Result := 1;
end else if AMsg.Msg = WM_TIMER then begin
if InterlockedExchange(fNotificationRequest, 0) = 0 then begin
// set to -1, so new message can be posted
InterlockedExchange(fNotificationRequest, -1);
// and kill timer
KillTimer(fNotificationWnd, 42);
end else begin
// new notifications have been requested - keep timer enabled
DoNotify;
end;
AMsg.Result := 1;
end else begin
with AMsg do
Result := DefWindowProc(fNotificationWnd, Msg, WParam, LParam);
end;
end;
procedure TMainThreadNotification.RequestNotification;
begin
if IsWindow(fNotificationWnd) then begin
if InterlockedIncrement(fNotificationRequest) = 0 then
PostMessage(fNotificationWnd, fNotificationMsg, 0, 0);
end;
end;
的類的實例可以被添加到TLogTarget
,調用在主線程通知事件,但每秒最多幾十倍。
代碼會很有幫助,至少在您調用同步和清理違規線程時會很有幫助。 – 2010-03-25 11:40:14