Jump to content
aehimself

AllocHwnd + TTimer = lag?

Recommended Posts

Hello all,

 

I have a progress indicator on a panel which is visible until a thread is running. On here is a timer which is updating the status of the thread every second (elapsed time, some data from inside the thread, etc.). The .OnTerminate event of said thread has a custom message sent to the panel so I can do some final touches plus freeing the thread object up:

 PostMessage(Self.Handle, UM_WORKERENDED, 0, 0);

Procedure TProgressPanel.UMWorkerEnded(Var Msg: TMessage);
Begin
 _worker.WaitFor;
 FreeAndNil(_worker);
End;

All was working fine until a recent refactoring, when I changed the panel to a TObject for modularity reasons (now said panel can display different content, so I had to move the progress indicator to it's own).

Since a TObject has no handle I created a dummy window with AllocHwnd and sending my message on this dummy handle. Everything works like clockwork, but I realized that the status update timer became laggy. Sometimes it refreshes too quickly (from second 1 to 2 in ~750 msec) and later on delaying more than 1 second (from second 2 to second 3 in 1,25 sec). Elapsed time is measured with a TStopWatch so I doubt the issue is with the measurement.

 

I am aware that the TTimer works via Window Messages - is it possible that adding a dummy window with AllocHwnd (which is picking up ALL window messages, not just the one specified) causes the code to simply be overwhelmed, not processing WM_TIMER in time?

 

Share this post


Link to post

I've had issues with having multiple allocated hWnds and messaging.

Ended up with a single allocated hWnd and a custom message broker/router for my user messages.

 

Share this post


Link to post
Posted (edited)
1 hour ago, aehimself said:

is it possible that adding a dummy window with AllocHwnd (which is picking up ALL window messages, not just the one specified) causes the code to simply be overwhelmed, not processing WM_TIMER in time?

If a window is dummy, it won't receive many messages. You can check it by logging every entrance to windowproc.

I hope you alloc a window in the context of main thread? Window handles belong to thread that created them.

Anyway do you also have timer inside worker thread? Or it is living in main thread and queries thread's status to display?

Edited by Fr0sT.Brutal

Share this post


Link to post
2 hours ago, aehimself said:

which is picking up ALL window messages, not just the one specified

It's not going to be sent many messages. 

 

Can you reproduce the behaviour in a cut down program?

Share this post


Link to post

Processing messages for whole application happens in one place. WM_TIMER is low priority message and it will be dispatched only after all higher priority messages are dispatched. The fact that WM_TIMER messages are not being processed fast enough means that you application is either flooded with messages or is doing to much work in main thread, including code called with TThread.Synchronize and TThread.Queue.

 

Changing from TPanel to TObject with allocated handle should not have any impact on processing speed. 

 

Also using WM_TIMER for progress is usually poor approach, it is better that you send status from the thread when you have some significant change to show. 

 

Additionally, if you are sending messages from OnTerminate event then you don't need to wait on thread (also freeing the thread will also wait).

 

It is hard to say more without having reproducible example.

  • Like 1

Share this post


Link to post
Posted (edited)

Thank you @Dalija Prasnikar, I learned some new things about window messages! Unfortunately though, none of the above applied to my case.

 

After the comment of @Lars Fosdal I got rid of the TTimer and used the same dummy window to catch the now manually fired SetTimer messages too. I also included a counter which increases each time a window message is received (whether processed or not) to see how swarmed application is. With a timer of 1 second, 70 seconds of runtime I got 72 messages. 1 is UM_WORKERENDED, I count the extra 1 as an acceptable discrepancy due to the timer's accuracy (or something unprocessed).

So no, it was not the window messages.

 

The solution is so painfully trivial I'm almost afraid to share...

 

In the TComponent my timer was set to 750 msec to update the elapsed time on the UI. As I'm using TStopWatch, I manually converted the .ElapsedMilliSeconds to some readable format... using dividing and substracting.

 

First time the timer fired at 0,75 seconds, UI was updated with the rounded 0 seconds.

Second time the timer fired at 1,5 seconds, UI was updated with the rounded 1 seconds.

Third time the timer fired at 2,25 seconds, UI was updated with the rounded 2 seconds.

Fourth time the timer fired at 3 seconds, UI was updated with the rounded 3 seconds.

Fourth time the timer fired at 3,75 seconds, UI was updated with the rounded 3 seconds.

 

Even if the timer component is spot on it can be seen that every 3 seconds, for 2 seconds the elapsed time does "not update"... thus the feeling of variable speed.

 

After upping the sampling to 1000 msec instead of 750 and some small tweaks to the conversion everything seems to be working fine.

 

Why it was working on the TPanel...? Because originally I forgot to change the default 1000 msec interval to 750...

 

What a nice way to waste a day by debugging something, which is working as it should 🙂

Edited by aehimself
  • Like 1

Share this post


Link to post

That is waaay to familiar.  As a developer, you never run out of chances to own yourself.

  • Like 1

Share this post


Link to post
5 hours ago, aehimself said:

I am aware that the TTimer works via Window Messages

Yes, and not only that, but it allocates its own window internally to handle those messages.  So it doesn't really matter if you create your own window or not, each window will handle only its own messages, but everything is going through a single message queue.

5 hours ago, aehimself said:

is it possible that adding a dummy window with AllocHwnd (which is picking up ALL window messages, not just the one specified) causes the code to simply be overwhelmed, not processing WM_TIMER in time?

Creating your own window will not cause such issues for TTimer.  And your own window will not pick up any messages that are not specifically targeting that window.  It is, however, possible that your own messages can cause delays in TTimer, because WM_TIMER is a low-priority synthesized message, so it will not be generated until the message queue is idle, unless you force it.  So don't overwhelm the message queue unnecessarily, regardless of which windows are involved.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×