Обработчик простоя Delphi срабатывает только при перемещении мыши

У меня есть обработчик OnIdle в моем приложении D2006. С этим кодом:

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

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

приложение работает без сбоев, обработчик простоя вызывается 100 раз в секунду, а загрузка ЦП близка к нулю.

Затем я добавил TActionList и подключил некоторые элементы управления к действиям, закодировал обработчик Execute и Update.

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

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

Проблема. Событие OnUpdate не срабатывает. По наитию я установил Done := true в обработчике OnIdle, и тогда обработчик OnIdle вызывается только при перемещении мыши. И действие «Обновить» по-прежнему не срабатывает.

Почему обработчик обновления может не срабатывать и следует ли установить для параметра «Готово» значение true или false? Или оба?


person rossmcm    schedule 31.03.2011    source источник
comment
Чего вы пытаетесь достичь в OnIdle?   -  person jachguate    schedule 01.04.2011
comment
Это остатки от другого проекта. Я просто имел его там на случай, если мне нужно было выполнить какую-то фоновую обработку. Он ничего не делает, кроме того, что вы видите — то есть ничего.   -  person rossmcm    schedule 01.04.2011
comment
OnIdle — ужасный способ фоновой обработки. Если вам нужно это сделать, вам лучше запустить отдельный поток. Это никак не влияет на взаимодействие с пользователем. (И Sleep в OnIdle — это ужасная вещь — она вызывает разбрызгивание или сбои в пользовательском интерфейсе.)   -  person Ken White    schedule 01.04.2011
comment
Я не понимаю, как задержка в 10 мс может вызвать сбои в пользовательском интерфейсе?   -  person rossmcm    schedule 01.04.2011
comment
Вы говорите о задержке в 10 мс каждый раз, когда срабатывает OnIdle, + накладные расходы на OnIdle и любой код в нем. Предположим, что ваш код OnIdle выполняется за 40 мс? Теперь вы увеличили его до 50 абсолютно без причины. Sleep всегда плохая идея, если для этого нет абсолютного требования; есть причина, по которой Windows и другие ОС поддерживают несколько потоков выполнения. Таким образом, фоновая обработка может выполняться в фоновом режиме, а не в потоке пользовательского интерфейса. А что произойдет, если пользователь переместит мышь так же, как вызывается OnIdle? Существует задержка перед обработкой события мыши. Глюк.   -  person Ken White    schedule 01.04.2011
comment
Я использую метод Sleep (10) в обработчике простоя, когда у меня есть что-то, что не займет много времени, и я хочу, чтобы это происходило на номинальной частоте, скажем, 100 Гц. Детализация таймера недостаточно хороша. У меня сложилось впечатление, что если вы вызываете Sleep (10), Windows оставляет вас в покое на 10 мс и делает другие вещи. Если бы у меня не было 10 мс, обработчик простоя вызывался бы яростно и с неопределенной скоростью. У меня никогда не бывает длительных задержек в обработчике простоя.   -  person rossmcm    schedule 01.04.2011
comment
Обработчик простоя вызывается только тогда, когда приложение бездействует (ожидает ввода данных пользователем, которому больше нечего делать) — поэтому он называется OnIdle. И если вы используете отдельный поток, вы можете заставить его ждать любое количество времени, которое вы хотите, используя события; вы сигнализируете о событии, когда поток должен что-то сделать, и он ждет, пока не заставит его что-то сделать. Таким образом, вы получаете всю желаемую степень детализации и не тратите циклы в основном потоке. Может быть, вам следует опубликовать еще один вопрос о преимуществах использования отдельного потока для фоновой обработки?   -  person Ken White    schedule 01.04.2011
comment
@ Дэвид, я получаю сообщение (!), но, пожалуйста, объясни почему. Что делать, если у меня есть быстро меняющиеся данные, которые я хочу отобразить на TLabel в пользовательском интерфейсе, я не могу сделать это из потока, не нуждаясь в Synchronize, PostMessage или критических разделах. Как я уже говорил ранее, я думал, что сон (10) означает забыть обо мне на (по крайней мере) 10 мс, и это подтверждается использованием ЦП, которое IIRC ниже, когда присутствует сон (10).   -  person rossmcm    schedule 01.04.2011
comment
@Ross Если вы хотите периодически что-то делать, используйте таймер.   -  person David Heffernan    schedule 01.04.2011
comment
@David Таймер не может работать лучше, чем 55 мс, или я так думал? Кроме того, я слышал, что они довольно требовательны к ресурсам (хотя это могло быть связано с Win16).   -  person rossmcm    schedule 01.04.2011
comment
@rossmcm: Если вам нужно быстро обновить TLabel, вы делаете это в своем основном потоке во время обработки, либо делая это напрямую (Label1.Caption := 'something'; Label1.Update;, либо используя синхронизацию из потока, как вы сказали. Если вы обновляете UI чаще, чем раз в секунду, вы, скорее всего, уже тратите слишком много времени; пользователь все равно не может следить за этим. Я не знаю, почему это продолжается здесь; @David и я оба советовали темы, что это совершенно отдельная тема, и в комментариях нет места для их обсуждения. Пожалуйста, задайте новый вопрос.   -  person Ken White    schedule 01.04.2011
comment
Обновление @ross на частоте 20 Гц будет абсолютно нормальным   -  person David Heffernan    schedule 01.04.2011
comment
@rssmcm: Если вам действительно нужно время ожидания не менее 10 миллисекунд между событием X, сохраните время выполнения времени X. В onidle проверьте с помощью GetTickCount, прошло ли 10 миллисекунд с момента последнего события. Если это так, сделайте X и обновите время последнего выполнения.   -  person The_Fox    schedule 01.04.2011


Ответы (3)


Как упоминалось в комментариях, Sleep в обработчике простоя не принесет пользы, также фоновая обработка остановится, если в приложении нет активности.

Однако вы можете снизить нагрузку на ЦП без особых мешающих эффектов:
после обработки всех событий OnIdle приложение вызовет WaitMessage (который будет спать, пока очередь сообщений пуста), если параметр Done равен True, вы можете просто безоговорочно установите его в своем обработчике.

Что касается фоновой обработки, используйте либо поток и обратный вызов основного потока через Synchronize, либо, если вам очень-очень нужно, используйте таймер и никогда не забывайте обрабатывать повторный вход (оба решения кстати разбудит приложение даже в то время как WaitMessage).

person Viktor Svub    schedule 01.04.2011

Используй источник, Люк. :)

Посмотрите на единицу Forms, особенно на единицу TApplication.Idle. Он содержит, в частности, следующее:

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;

Как видите, DoActionIdle вызывается только при Done = True and FActionUpdateDelay <= 0 или IdleTimerHandle = 0. DoActionIdle (также часть TApplication) вызывает UpdateAction. Поэтому, если ни одно из вышеперечисленных условий не выполняется, TAction.OnUpdate никогда не вызывается.

Есть отдельный метод, TApplication.DoMouseIdle, который вы также можете изучить.

person Ken White    schedule 31.03.2011
comment
Все события TApplication(Events).OnIdle запускаются каждый раз при вводе этого кода с помощью вызова FOnIdle(Self, Done). DoActionIdle просто обновляет/запускает различные компоненты TAction в любых видимых в данный момент формах, что в данном случае не представляет большого интереса. - person Viktor Svub; 01.04.2011
comment
@ Виктор, нет. Вы упускаете суть исходного вопроса. Этот код запускает Application(Events).OnIdle и затем на основе значения, возвращенного в Done, решает, что делать дальше (что определяет, будет ли запущено событие DoActionIdle, которое обновляет действия в формах, это то, о чем был первоначальный вопрос в первую очередь. :) - person Ken White; 01.04.2011
comment
А, mae culpa... Я слишком зациклился на первой половине вопроса, вы совершенно правы :) - person Viktor Svub; 02.04.2011

Избавьтесь от этого обработчика события OnIdle, вы приняли его на всякий случай.

Если позже вам потребуется выполнять фоновые задачи, узнайте, как использовать потоки. Чтобы получить определенную частоту, вам разрешено использовать сон или любую другую технику в потоке.

Мой совет таков, потому что, как вы видите, такой способ работы мешает другим частям вашего приложения. Если это ошибка в TApplication, я не знаю, может быть, это так. Если вы хотите узнать больше, сделайте копию своего проекта, проверьте все, и если вы считаете, что это должно работать по-другому, заполните запись контроля качества об этом.

Я просматривал исходный код XE, и кажется, что все в порядке, они устанавливают событие для обновления действий, если событие Idle не выполняется. Я не вижу там ошибки. У меня нет готовых установок до 2010 года для проверки древних версий.

person jachguate    schedule 01.04.2011