3

I have an OnIdle handler in my D2006 app. With this code:

procedure TMainForm.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);

begin
Inc (IdleCalls) ;
Sleep (10) ;
Done := False ;
end ;

the app runs smoothly, the idle handler is called 100 times per second, and the CPU usage is next to zero.

I then added a TActionList and connected up some controls to actions, coded an Execute and Update handler.

procedure TMainForm.ActionNewButtonExecute(Sender: TObject);
begin
DoNewProject ;
end ;

procedure TMainForm.ActionNewButtonUpdate(Sender: TObject);
begin
ActionNewButton.Enabled := AccessLevelIsSupervisor ;
end;

Problem. The OnUpdate event doesn't fire. On a hunch I set Done := true in the OnIdle handler and the OnIdle handler is then only called when I move the mouse. And the Update action still doesn't fire.

Why might the Update handler not be firing, and should I set Done to true or false? Or both?

menjaraz
  • 7,551
  • 4
  • 41
  • 81
rossmcm
  • 5,493
  • 10
  • 55
  • 118
  • 1
    What are you trying to accomplish in OnIdle? – jachguate Mar 31 '11 at 23:54
  • It's a remnant from another project. I just had it there in case there was some background processing I needed to do. It does nothing apart from what you see - i.e. nothing. – rossmcm Apr 01 '11 at 00:02
  • 3
    OnIdle is a terrible way to do background processing. If you need to do that, you're better off launching a separate thread. It doesn't affect the user interaction in any way. (And `Sleep` in OnIdle is a *terrible* thing - it causes "sputtering" or glitches in the UI.) – Ken White Apr 01 '11 at 00:13
  • I don't see how a 10mS delay would cause glitches in the UI? – rossmcm Apr 01 '11 at 01:05
  • You're talking about a 10ms delay every time OnIdle fires, + the overhead of OnIdle and any code in it. Suppose your OnIdle code takes 40ms to execute? You've now increased it to 50 for absolutely no reason. `Sleep` is always a bad idea unless there's an absolute requirement for it; there's a reason Windows and other OSes support multiple threads of execution. It's so background processing can be done in the *background*, not the UI thread. And what happens if the user happens to move the mouse just as OnIdle is called? There's a delay before the mouse event is processed. A glitch. – Ken White Apr 01 '11 at 01:13
  • I use the technique of a Sleep (10) in the idle handler when I have something that doesn't take long and I want to happen at say 100Hz nominally. A timer's granularity isn't fine enough. I was under the impression that if you called Sleep (10), windows left you alone for 10ms and did other things. If I didn't have the 10mS in there, the idle handler would be called furiously, and at an indeterminate rate. I never have long delays in the idle handler. – rossmcm Apr 01 '11 at 01:27
  • The idle handler is only called when the application is idle (waiting for user input with nothing else to do) - that's why it's called OnIdle. And if you use a separate thread, you can have it wait for any amount of time you want using events; you signal the event when the thread should do something, and it waits until it gets it to do anything. That way you get all the granularity you want, and you're not wasting cycles in the main thread. Maybe you should post another question about the benefits of using a separate thread for background processing? – Ken White Apr 01 '11 at 01:38
  • @David, I'm getting the message (!), but please explain why. What if I have rapidly changing data that I want to display on a TLabel in the UI, I can't do that from a thread without needing Synchronize or PostMessage or critical sections. As I said before, I had thought that sleep (10) meant "forget about me for (at least) 10 mS and this is borne out by the CPU usage, which IIRC is lower when Sleep (10) is present. – rossmcm Apr 01 '11 at 09:37
  • @Ross If you want to do things periodically use a timer. – David Heffernan Apr 01 '11 at 09:45
  • @David A timer can't do any better than 55 mS or so I thought? Also, I had heard they were quite resource-intensive (although that might have been a Win16 thing) – rossmcm Apr 01 '11 at 10:25
  • @rossmcm: If you have to rapidly update a TLabel, you do that in your main thread while you're processing, either by doing it there directly (`Label1.Caption := 'something'; Label1.Update;`, or use Synchronize from the thread as you said. If you're updating the UI more often than once a second, you're most likely already wasting way too much time; the user can't follow that anyway. I don't know why this is continuing here; @David and I both advised threads, which is a totally separate topic, and there's no room here in comments to discuss them. Please post a new question. – Ken White Apr 01 '11 at 11:02
  • @ross updating at 20Hz is going to be absolutely fine – David Heffernan Apr 01 '11 at 11:04
  • @rssmcm: If you really want at least 10 miliseconds of wait time between event X, save the execution time of time X. In the onidle, check with GetTickCount if 10 miliseconds have past since last time. If so, do X and update last execution time. – The_Fox Apr 01 '11 at 13:12
  • http://delphiforfun.org/programs/delphi_techniques/OnIdleDemo.htm – Gabriel Apr 05 '22 at 11:25

3 Answers3

6

Use the source, Luke. :)

Look at the Forms unit, specifically TApplication.Idle. It contains, in part, the following:

Done := True;
try
  if Assigned(FOnIdle) then FOnIdle(Self, Done);
  if Done then
    if FActionUpdateDelay <= 0 then
      DoActionIdle
  // Excluded to avoid copyright violation
  // See also the else portion, which contains (in part)
  else
    if IdleTimerHandle = 0 then
    begin
      IdleTimerHandle := SetTimer(0, 0, FActionUpdateDelay, IdleTimerDelegate);
      if IdleTimerHandle = 0 then
        DoActionIdle
    end;
finally
  // Omitted
end;

As you can see, DoActionIdle is only called when either Done = True and FActionUpdateDelay <= 0 or IdleTimerHandle = 0. DoActionIdle (also part of TApplication) is what calls UpdateAction. So if neither of the above conditions are met, TAction.OnUpdate is never called.

There's a separate method, TApplication.DoMouseIdle, that you may want to peruse as well.

Ken White
  • 123,280
  • 14
  • 225
  • 444
  • All the `TApplication(Events).OnIdle` event/s are fired everytime this code is entered, via the `FOnIdle(Self, Done)` call. `DoActionIdle` just updates/fires the different TAction components in any currently visible forms, which is of little interest in this case. – Viktor Svub Apr 01 '11 at 11:04
  • @Viktor, no. You're missing the point of the original question. This code fires the Application(Events).OnIdle, and *then* decides based on the value returned in Done what to do next (which decides whether the DoActionIdle event is fired, which updates the actions on the forms, which is what the original question was about in the first place. :) – Ken White Apr 01 '11 at 14:43
  • Ah, mae culpa... I was too focused on the first half of the question, you're completely right :) – Viktor Svub Apr 02 '11 at 08:45
3

As mentioned in the comments, Sleep in the idle handler will do no good, also the bacground processing will stall if there is no activity on the application.

You can however lower the CPU usage w/o much disturbing effects:
After processing all OnIdle events, the application will call WaitMessage (which will sleep while the message queue is empty), if the Done parameter is True - you can just unconditionally set it in your handler.

As for background processing, use either a thread and call back to the main thread via Synchronize or, if you really-really have to, use a timer and don't ever forget to handle reentrancy (both solutions will by the way wake the application even while WaitMessage).

Viktor Svub
  • 1,451
  • 10
  • 15
  • Small hint: Using a TTimer has no re-entrancy problem, UNLESS you use Application.ProcessMessages in your code. – Gabriel Apr 05 '22 at 11:05
2

Get rid of that OnIdle event handler, you accepted it is there just in case.

If you later need to perform background tasks, learn how to use threads. To get a specific frequency, you're allowed to use sleep or any other technique within a thread.

My advice is in this way because, as you see, that way of do things is interfering with other parts of your application. If it is a bug in the TApplication, I don't know, maybe it is. If you want to investigate more, make a copy of your project, check everything and if you think this have to work another way, fill a QC entry about that.

I was looking the XE source code and it seems Ok, they set an event to update the actions if the Idle event is not done.. I don't see a bug there. I have no pre-2010 ready installations to check ancient versions.

jachguate
  • 16,976
  • 3
  • 57
  • 98