Recent

Author Topic: Application.QueueAsyncCall working incorrectly?  (Read 339 times)

hedgehog

  • Full Member
  • ***
  • Posts: 124
Application.QueueAsyncCall working incorrectly?
« on: May 09, 2026, 04:21:17 pm »
Hi

Let's say I want to draw a clock on the screen without using a timer.
Since calling the Invalidate function from the OnPaint event is a bad idea, I tried to do it with an asynchronous call.

Everything works well, except for one thing - the close window button does not work.

What am I doing wrong?
After all, an AsyncCall introduces a new message into the queue, right?

(Win64 + Laz4.4)

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     procedure FormPaint(Sender: TObject);
  16.   private
  17.     procedure DoRepaint(Data: PtrInt);
  18.   public
  19.  
  20.   end;
  21.  
  22. var
  23.   Form1: TForm1;
  24.  
  25. implementation
  26.  
  27. {$R *.lfm}
  28.  
  29. { TForm1 }
  30.  
  31. procedure TForm1.DoRepaint(Data: PtrInt);
  32. begin
  33.   //Application.ProcessMessages;  // not worked
  34.   Invalidate;
  35.   //Application.ProcessMessages;  // stack overflow after 0.1 sec !!!
  36. end;
  37.  
  38. procedure TForm1.FormPaint(Sender: TObject);
  39. var
  40.   p1, p2: TPoint;
  41.   angleSec, angleMin, angleHour: double;
  42. begin
  43.   angleHour:= 4*pi*frac(double(time));
  44.   angleMin:=  angleHour*12;
  45.   angleSec:=  angleMin*60;
  46.   Canvas.Ellipse(ClientRect);
  47.   p1:= ClientRect.CenterPoint;
  48.  
  49.   p2.x:= p1.X  + trunc(p1.X*sin(angleSec)*0.95);
  50.   p2.y:= p1.y  - trunc(p1.y*cos(angleSec)*0.95);
  51.   Canvas.Line(p1, p2);
  52.  
  53.   p2.x:= p1.X  + trunc(p1.X*sin(angleMin)*0.75);
  54.   p2.y:= p1.y  - trunc(p1.y*cos(angleMin)*0.75);
  55.   Canvas.Line(p1, p2);
  56.  
  57.   p2.x:= p1.X  + trunc(p1.X*sin(angleHour)*0.5);
  58.   p2.y:= p1.y  - trunc(p1.y*cos(angleHour)*0.5);
  59.   Canvas.Line(p1, p2);
  60.  
  61.   Application.QueueAsyncCall(@DoRepaint, 0);
  62. end;
  63.  
  64. end.
« Last Edit: May 09, 2026, 04:22:56 pm by hedgehog »

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Re: Application.QueueAsyncCall working incorrectly?
« Reply #1 on: May 09, 2026, 04:54:24 pm »
If it compiles! that should work except from closing the application too early. Can you attach a full project that does not work? Your example code is not complete and I am not in guessing mode.

Note your invalidate part is correct: calling repaint will probably cause a stack overflow because it keeps adding paint messages to the message queue on every change.

If that is in your opinion really necessary, use a timer and not asynccall. Messages get processed in ms range and your eyes can not keep up with that anyway: set the timer to cover not more than 60 FPS so not more that 60 repaints per second. 25 will do..except for high speed games code.
« Last Edit: May 09, 2026, 05:05:57 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12429
  • Debugger - SynEdit - and more
    • wiki
Re: Application.QueueAsyncCall working incorrectly?
« Reply #2 on: May 09, 2026, 06:05:59 pm »
The stack overflow wouldn't be unexpected.

QueueAsyncCall add the message to the queue AND adds an event to the messages to make sure it will be processed.

ProcessMessages then picks up that event. But within it, another cycle starts and so on...

-------------------
Have you tested with 4.99?  Especially with regards to the blocked close button?

I recall there was a change, because of a similar deadlock.

-------------------

I don't understand why you need to invalidate after the paint? That means even if it works, you go at 100% CPU load (for one CPU core).

What is the reason for not using a timer???
But even then, some  "sleep(100);" somewhere would be a good idea. (Adjust it, if the next update is less ms away)

And 100 ms sleep would hardly be noticeable with regards to the close button (once that works at all).


Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Re: Application.QueueAsyncCall working incorrectly?
« Reply #3 on: May 09, 2026, 06:43:42 pm »
Better wording. Same effect. Tnx.
objects are fine constructs. You can even initialize them with constructors.

hedgehog

  • Full Member
  • ***
  • Posts: 124
Re: Application.QueueAsyncCall working incorrectly?
« Reply #4 on: May 09, 2026, 07:04:11 pm »
Hi all.

I have attached the project. There's nothing there, just the form and the OnPaint event.

P.S. Of course, I know all the ways to use a timer for this. But to be honest, it's not elegant.
« Last Edit: May 09, 2026, 07:23:49 pm by hedgehog »

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Re: Application.QueueAsyncCall working incorrectly?
« Reply #5 on: May 10, 2026, 06:26:12 am »
Thanks for adding the code, will look did look into it, but why is a timer not "elegant"? It is how it should be done in the first place.
See attachment. Took 25 seconds.

You will see I removed some of your code, added a timer and added begin/endformupdate. Simple and elegant.
The timer does the invalidate. It also does take almost no cpu time.
« Last Edit: May 10, 2026, 06:59:45 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

hedgehog

  • Full Member
  • ***
  • Posts: 124
Re: Application.QueueAsyncCall working incorrectly?
« Reply #6 on: May 10, 2026, 01:15:18 pm »
Hi, Thaddy.

Of course, your timer example works great!
I was interested in other options for organizing the rendering cycle. (I know about OnIdle)

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Re: Application.QueueAsyncCall working incorrectly?
« Reply #7 on: May 10, 2026, 01:26:00 pm »
In that case, just note the begin/endformupdate that I added.
objects are fine constructs. You can even initialize them with constructors.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12429
  • Debugger - SynEdit - and more
    • wiki
Re: Application.QueueAsyncCall working incorrectly?
« Reply #8 on: May 10, 2026, 02:57:48 pm »
I looked at the original case...

Clicking close (or any other close action) will set a flag. The message loop will then
- finish all events
- call any pending QueueAsyncCalls  (and that may be important, as they may do clean up work, and avoid error on exit)
- check the flag and exit the app

Only, you keep adding QueueAsyncCalls.

I don't know if that should be considered a bug in the LCL... since after all there is good reason to finish any pending work.




Despite my clear objection to running a 100% CPU load loop like your code did, you can fix your original code

At the end of paint (or for any QueueAsyncCall)
Code: Pascal  [Select][+][-]
  1. if not Application.Terminated then
  2.   Application.QueueAsyncCall(@DoRepaint, 0);
Then you will no longer tell the app control that you have more work before the app can exit.



You can also remove any remaining "Application.ProcessMessages" => not needed.

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
Re: Application.QueueAsyncCall working incorrectly?
« Reply #9 on: May 10, 2026, 03:17:23 pm »
I looked at that too: did you check wasted cpu cycles? I know that answer.
objects are fine constructs. You can even initialize them with constructors.

 

TinyPortal © 2005-2018