我的问题是,如果一个线程快速地向主UI线程发布消息并且我在此时更新UI,有时主消息队列会卡住(我没有更好的词来描述这个问题)。
以下是简化的重现代码:
const
TH_MESSAGE = WM_USER + 1; // Thread message
TH_PARAM_ACTION = 1;
TH_PARAM_FINISH = 2;
type
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
ThreadHandle: Integer;
procedure ThreadMessage(var Message: TMessage); message TH_MESSAGE;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function ThreadProc(Parameter: Pointer): Integer;
var
ReceiverWnd: HWND;
I: Integer;
Counter: Integer;
begin
Result := 0;
ReceiverWnd := Form1.Handle;
Counter := 100000;
for I := 1 to Counter do
begin
PostMessage(ReceiverWnd, TH_MESSAGE, TH_PARAM_ACTION, I);
//Sleep(1); // <- is this the cure?
end;
PostMessage(ReceiverWnd, TH_MESSAGE, TH_PARAM_FINISH, GetCurrentThreadID);
OutputDebugString('Thread Finish OK!'); // <- I see this
EndThread(0);
end;
procedure TForm1.ThreadMessage(var Message: TMessage);
begin
case Message.WParam of
TH_PARAM_ACTION:
begin
Label1.Caption := 'Action' + IntToStr(Message.LParam);
//Label1.Update;
end;
TH_PARAM_FINISH:
begin
OutputDebugString('ThreadMessage Finish'); // <- Dose not see this
Button1.Enabled := True;
CloseHandle(ThreadHandle);
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ThreadId: LongWord;
begin
Button1.Enabled := False;
ThreadId := 1;
ThreadHandle := BeginThread(nil, 0, @ThreadProc, nil, 0, ThreadId);
end;
我意识到工作线程循环非常繁忙。我认为由于该线程正在将消息“发布”到主UI线程,因此(主UI线程)有机会在接收来自工作线程的其他消息时处理其消息。
随着计数器的增加,问题不断升级。
问题:
除非我添加Label1.Update,否则我从未看到Label1被更新;而且主UI被阻塞。
TH_PARAM_ACTION从未达到100000(在我的情况下),而是在90000以上随机上升。
TH_PARAM_FINISH从未到达消息队列。
显然,CPU使用率非常高。
问题:
如何正确处理这种情况?从工作线程发布的消息是否已从消息队列中删除(如果是,则为什么)?
循环中的Sleep(1)真的是解决此问题的方法吗?如果是,那么为什么是1?(0不行)
好的。感谢@Sertac和@LU,我现在意识到消息队列有一个限制,并且现在使用ERROR_NOT_ENOUGH_QUOTA检查来自PostMessage的结果。但是,主UI仍然没有响应!
function ThreadProc(Parameter: Pointer): Integer;
var
ReceiverWnd: HWND;
I: Integer;
Counter: Integer;
LastError: Integer;
ReturnValue, Retry: Boolean;
begin
Result := 0;
ReceiverWnd := Form1.Handle;
Counter := 100000;
for I := 1 to Counter do
begin
repeat
ReturnValue := PostMessage(ReceiverWnd, TH_MESSAGE, TH_PARAM_ACTION, I);
LastError := GetLastError;
Retry := (not ReturnValue) and (LastError = ERROR_NOT_ENOUGH_QUOTA);
if Retry then
begin
Sleep(100); // Sleep(1) is not enoght!!!
end;
until not Retry;
end;
PostMessage(ReceiverWnd, TH_MESSAGE, TH_PARAM_FINISH, GetCurrentThreadID);
OutputDebugString('Thread Finish OK!'); // <- I see this
EndThread(0);
end;
仅供参考,这是我检查的原始代码:
Delphi threading by example
这个示例程序可以在多个文件中搜索文本(同时使用5个线程)。显然,当你执行这样一个任务时,你必须看到所有匹配的结果(例如,在 ListView 中)。
问题在于,如果我在很多文件中搜索,而搜索字符串很短(比如 "a") - 就会找到很多匹配项。繁忙的循环 while FileStream.Read(Ch,1)= 1 do
会快速地发布消息(TH_FOUND
),其中包含匹配项,并使消息队列淹没。
实际上,这些消息并没有进入消息队列。就像 @Sertac 所提到的那样,“消息队列默认有10000个限制”。
来自 MSDN 的PostMessage
每个消息队列最多可以发布10000个消息。这个限制应该足够大。如果您的应用程序超过了这个限制,它应该重新设计以避免消耗太多的系统资源。要调整此限制,请修改以下注册表键(USERPostMessageLimit)
正如其他人所说,这段代码/模式应该重新设计。