My problem is that if a thread Posting messages rapidly to the main UI thread and if I update the UI at that time, sometimes the main message queue get stuck (I have no better words to describe this).
Here is the simplified repro code:
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;
I do realize that the worker thread loop is very busy. I thought that since the thread is Posting messages to the main UI thread, it (the main UI thread) has the chance to process it's messages while receiving other messages from the worker thread.
The problem escalates as I increase the counter.
Problems:
I never see Label1
being updated unless I add Label1.Update
; and the main UI is blocked.
TH_PARAM_ACTION
never reaches 100000 (in my case) - randomly above 90000.
TH_PARAM_FINISH
never gets to the message queue.
Obviously the CPU usage is very high.
Questions:
What is the correct way to handle this situation? Are messages posted from the worker thread being removed from the message queue (if yes, then why)?
Is Sleep(1)
in the loop is really the cure to this problem? if yes then why 1
? (0
does not)
OK. Thanks to @Sertac and @LU I now realize that the message queue has a limit, and now checking for result from PostMessage
with ERROR_NOT_ENOUGH_QUOTA
.but, still the main UI is NOT responsive!
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;
Just for reference here is the original code I was inspecting:
Delphi threading by example
This example searches text in files (5 threads simultaneously). Obviously when you make a task like that, you must see all the matching results (in a ListView for example).
Problem was that if I searched in meany files, and the search string was short (like "a") - there were meany matches found. the busy loop while FileStream.Read(Ch,1)= 1 do
was posting messages rapidly (TH_FOUND
) with the match and flooding the
message queue.
Messages where in fact not getting to the message queue. as @Sertac mentioned "a message queue has a limit of 10000 by default".
From MSDN PostMessage
There is a limit of 10,000 posted messages per message queue. This limit should be sufficiently large. If your application exceeds the limit, it should be redesigned to avoid consuming so many system resources. To adjust this limit, modify the following registry key (USERPostMessageLimit)
As others said, this code/pattern should be redesigned.
Sleep
in a thread is too risky. – Nolenif i mod 10000 = 0 then PostMessage
... is this the correct pattern? (but then I loose the info I want in my main thread :/ ) – CeriseSendMessage
to inform the main thread. If a new message from the first thread arrives, check for obsolete messages in the queue and remove them. – Alcove