【发布时间】:2014-09-20 12:14:19
【问题描述】:
我的问题是,如果一个线程快速将消息发布到主 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,我现在意识到消息队列有限制,现在检查来自PostMessage 和ERROR_NOT_ENOUGH_QUOTA 的结果。但是,主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
每个消息队列的发布消息限制为 10,000 条。这 限制应该足够大。如果您的申请超过 限制,应该重新设计以避免消耗这么多系统 资源。要调整此限制,请修改以下注册表项 (USERPostMessageLimit)
正如其他人所说,应该重新设计此代码/模式。
【问题讨论】:
-
"从工作线程发布的消息是否从消息队列中删除(如果是,那么为什么)?" 是的,消息队列默认限制为 10000 (搜索 USERPostMessageLimit)。
-
最好检查 PostMessage 的结果,或者将消息放在自己的队列中。
-
仅供参考 普通用户每秒可以识别 25 张图像。如果用户界面的变化比这更快,一些信息将永远不会到达用户;o)
-
这可能是 Sleep(0) 不够的原因,它只会产生。 Sleep(1) 实际上是 Sleep(10)(时钟分辨率),这可能足以让主线程清空队列,然后处理挂起的绘制消息。我想我最后会使用 Sleep 和“mod”。
-
@SertacAkyuz 如果“反馈”快于用户可以识别的速度,则表示没有反馈。牢记系统和用户的限制并没有过度设计。
标签: multithreading delphi user-interface delphi-7