Thông báo ghi nhật ký chỉ là một trong những khu vực mà ở đó Synchronize()
không có ý nghĩa gì cả. Thay vào đó, bạn nên tạo một đối tượng mục tiêu nhật ký, trong đó có một danh sách chuỗi, được bảo vệ bởi một phần quan trọng và thêm các thông điệp tường trình của bạn vào đó. Có chủ đề VCL chính loại bỏ các thông điệp tường trình từ danh sách đó và hiển thị chúng trong cửa sổ nhật ký. Này có một số ưu điểm:
Bạn không cần phải gọi Synchronize()
, mà chỉ là một ý tưởng tồi. Hiệu ứng phụ tốt đẹp là loại vấn đề tắt máy của bạn biến mất.
Chủ đề người lao động có thể tiếp tục công việc của mình mà không bị chặn xử lý sự kiện chủ đề chính hoặc trên các chủ đề khác đang cố đăng nhập thư.
Tăng hiệu suất, vì nhiều thư có thể được thêm vào cửa sổ nhật ký trong một lần. Nếu bạn sử dụng BeginUpdate()
và EndUpdate()
điều này sẽ tăng tốc độ.
Không có bất lợi nào mà tôi có thể thấy - thứ tự của thông điệp tường trình cũng được giữ nguyên.
Edit:
tôi sẽ bổ sung thêm một số chi tiết thông tin và một chút mã để chơi với, để minh họa rằng có những cách tốt hơn để làm những gì bạn cần phải làm.
Gọi Synchronize()
từ một luồng khác với chuỗi ứng dụng chính trong chương trình VCL sẽ làm cho chuỗi gọi bị chặn, mã được truyền sẽ được thực hiện trong ngữ cảnh của chuỗi VCL, và sau đó chuỗi gọi sẽ được bỏ chặn và tiếp tục chạy. Đó có thể là một ý tưởng hay trong thời gian của các máy xử lý đơn lẻ, mà chỉ có một luồng có thể chạy cùng lúc, nhưng với nhiều bộ xử lý hoặc lõi thì đó là một sự lãng phí khổng lồ và nên tránh bằng mọi giá. Nếu bạn có 8 chủ đề công nhân trên một máy 8 lõi, khi họ gọi Synchronize()
có thể sẽ giới hạn thông lượng tới một phần nhỏ của những gì có thể.
Thực ra, gọi Synchronize()
không bao giờ là ý tưởng hay vì nó có thể dẫn đến deadlocks. Một lý do thuyết phục hơn để không sử dụng nó, bao giờ hết.
Sử dụng PostMessage()
để gửi các thông điệp log sẽ chăm sóc của vấn đề bế tắc, nhưng nó có vấn đề riêng của mình:
Mỗi chuỗi log sẽ gây ra một thông điệp tới được đăng tải và xử lý, gây ra nhiều chi phí. Không có cách nào để xử lý một số thông điệp tường trình trong một lần.
Thông báo của Windows chỉ có thể mang dữ liệu có kích thước bằng máy trong các tham số. Do đó, việc gửi chuỗi là không thể. Việc gửi các chuỗi sau khi nhập một mẫu đến PChar
là không an toàn, vì chuỗi có thể đã được giải phóng vào lúc thông báo được xử lý. Phân bổ bộ nhớ trong luồng công nhân và giải phóng bộ nhớ đó trong chuỗi VCL sau khi thông báo đã được xử lý là một lối thoát. Một cách làm tăng thêm chi phí.
Hàng đợi thư trong Windows có kích thước hữu hạn. Đăng quá nhiều thư có thể dẫn đến hàng đợi trở nên đầy đủ và thư bị xóa. Đó không phải là một điều tốt, và cùng với điểm trước đó nó dẫn đến rò rỉ bộ nhớ.
Tất cả thư trong hàng đợi sẽ được xử lý trước khi bất kỳ bộ đếm thời gian hoặc tin nhắn vẽ nào sẽ được tạo. Một luồng ổn định của nhiều tin nhắn được đăng có thể khiến chương trình trở nên không phản hồi.
Một cấu trúc dữ liệu thu thập các thông điệp đăng nhập có thể trông như thế này:
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;
Nhiều chủ đề có thể gọi LogMessage()
đồng thời, vào cấu hình phần quan trọng sẽ serialize truy cập vào danh sách, và sau khi thêm thông điệp của họ các chủ đề có thể tiếp tục với công việc của họ.
Điều đó để lại câu hỏi cách chuỗi VCL biết khi nào cần gọi GetLoggedMsgs()
để xóa thư khỏi đối tượng và thêm chúng vào cửa sổ. Một phiên bản của người nghèo sẽ có một bộ đếm thời gian và bình chọn. Một cách tốt hơn là gọi PostMessage()
khi thông điệp tường trình được thêm vào:
procedure TLogTarget.LogMessage(const AMsg: string);
begin
fCritSect.Enter;
try
fMsgs.Add(AMsg);
PostMessage(fNotificationHandle, WM_USER, 0, 0);
finally
fCritSect.Leave;
end;
end;
Điều này vẫn gặp sự cố với quá nhiều thư được đăng.Chỉ cần đăng thông báo khi tin nhắn trước đó đã được xử lý:
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;
Điều đó vẫn có thể được cải thiện. Sử dụng bộ hẹn giờ giải quyết được vấn đề của các tin nhắn đã đăng tải sẽ lấp đầy hàng đợi. Sau đây là một lớp học nhỏ mà thực hiện điều này:
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;
Một thể hiện của lớp có thể được thêm vào TLogTarget
, để gọi một sự kiện thông báo trong các chủ đề chính, nhưng thời gian tối đa là vài chục mỗi giây.
Mã sẽ hữu ích, ít nhất là với cuộc gọi của bạn để Đồng bộ hóa và xóa sạch chuỗi vi phạm. –