Переполнение стека Delphi из-за цикла обработки событий

Я работаю над приложением, которое имеет два списка. Я загружаю два списка со значениями, и когда я продолжаю щелкать элементы из списка, я получаю следующую ошибку при отладке.

введите здесь описание изображения

Запуск exe приводит к закрытию приложения. Иногда я получаю сообщение «Нарушение прав доступа».

Итак, что мне делать, чтобы избавиться от этой ошибки в моем приложении?

ИЗМЕНИТЬ

..

В основной форме есть таймер, который обновляет все элементы управления timer_RefreshCOntrol (интервал 1).

всякий раз, когда editBox_one изменяется (значение), эта функция вызывается

Procedure TStringSetting.SetValue (const AValue : String);
  Begin
   ...
    If FValueControl <> Nil then
    Begin
     FValueControl.OnChange := VoidNotifyEvent;
     FValueControl.Text := NewValue;
     FValueControl.OnChange := EditChange;        //<--here the stackoverflow error comes....
    end;
  end;




 Procedure EditChange (Sender: TObject);
   Begin
       Value := FValueControl.Text;
       If Not EditIsValid then FValueControl.Font.Color := clRed
       else If Dirty  then FValueControl.Font.Color := clBlue
                  else FValueControl.Font.Color := clWindowText;

       If @OldCustomEditChange <> Nil then OldCustomEditChange(Sender);
    end;`


   the EditChange (Sender: TObject); <--keeps geting called and the stackoverflow error comes

EditChange назначается на поле редактирования FormCreate

ИЗМЕНИТЬ2

Я не первоначальный разработчик. Иногда я просто обрабатывал код, серьезный рефакторинг невозможен.

редактировать 3 Значение стека вызовов, но что такое "???" введите здесь описание изображения

ИЗМЕНИТЬ 4

после прохождения @Cosmin Prund и @david

У меня есть место, где начинается звонок бесконечности

   Procedure TFloatSetting.EditChange (Sender: TObject);
  Begin
    SkipNextOnChange := True;
  Inherited EditChange(Sender);
  IfValidThenStore(FValueControl.Text);
  Inherited EditChange(Sender);  {<-------This is where it start}
 end;


 Procedure TStringSetting.EditChange (Sender: TObject);
  Begin
   Value := FValueControl.Text;
   If Not EditIsValid then FValueControl.Font.Color := clRed
     else If Dirty  then FValueControl.Font.Color := clBlue
                  else FValueControl.Font.Color := clWindowText;

   If @OldCustomEditChange <> Nil then OldCustomEditChange(Sender); {<---this keeps calling  Procedure TFloatSetting.EditChange (Sender: TObject);}
 end;

person Community    schedule 08.03.2012    source источник
comment
Совершенно невозможно сказать с этой небольшой информацией! Конечно, списки Windows не глючат. Вам нужно показать свой конкретный код.   -  person Andreas Rejbrand    schedule 08.03.2012
comment
Хорошо, во-первых, использование таймера (особенно с интервалом 1 — это тысяча вызовов в секунду, что даже больше, чем вы можете получить с TTimer, и такая трата процессорного времени) ужасно для этого . В любом случае, скорее всего, у вас есть процедура, которая вызывает сама себя, или какая-то другая процедура, которая вызывает первую процедуру. Это должно быть достаточно легко найти. Вероятно, EditChange вызывает вызов EditChange, который вызывает вызов EditChange, и так далее до бесконечности. Чтобы убедиться в этом, добавьте beep; sleep(1000) в начало EditChange. (продолжение)   -  person Andreas Rejbrand    schedule 08.03.2012
comment
(Продолжение) Если моя гипотеза верна, вы должны получать один звуковой сигнал в секунду, пока не уничтожите свою программу.   -  person Andreas Rejbrand    schedule 08.03.2012
comment
это приложение в реальном времени, и требуется интервал 1.   -  person    schedule 08.03.2012
comment
Хорошо, но даже если вы установите интервал равным 1, вы не получите тысячу вызовов в секунду, потому что это выше разрешения таймера Windows. Вы не заметите никаких изменений, если измените интервал на 30.   -  person Andreas Rejbrand    schedule 08.03.2012
comment
да, таймер обновит элементы управления приложением, такие как рисование и кнопки включения / выключения ... да пустая трата времени процессора ... но можно ли эту ошибку обработать попыткой?   -  person    schedule 08.03.2012
comment
try..exccept, ты имеешь в виду? Нет, не может. Вам нужно найти причину проблемы.   -  person Andreas Rejbrand    schedule 08.03.2012
comment
Что произошло, когда вы добавили beep; sleep(1000) в начало EditChange?   -  person Andreas Rejbrand    schedule 08.03.2012
comment
Ошибки переполнения стека обычно очень легко отследить: создайте приложение со всеми включенными параметрами отладки, когда появится ошибка, посмотрите на стек вызовов. Скорее всего, вы увидите цикл похожих функций.   -  person Cosmin Prund    schedule 08.03.2012
comment
Я собирался сказать то же самое, что и Космин!!   -  person David Heffernan    schedule 08.03.2012
comment
Вам не нужно использовать такой таймер, и если бы это приложение было разработано должным образом, у вас, вероятно, не было бы этих проблем. Когда кто-то вроде Андреаса дает подобные советы, вы должны слушать и рассматривать это как возможность чему-то научиться.   -  person David Heffernan    schedule 08.03.2012
comment
BeepSleep: Потрясающая техника отладки.   -  person Warren P    schedule 08.03.2012


Ответы (3)


Основываясь на размещенном стеке вызовов, очевидно, почему происходит ошибка: TStringSetting.EditChange вызывает TFloatSetting.EditChange, а это, в свою очередь, вызывает TStringSetting.EditChange. Цикл продолжается до тех пор, пока все пространство стека не будет исчерпано.

Вот несколько советов о том, почему это может произойти, а также советы о том, как это исправить:

  • Возможно, задействованные элементы управления запускают обработчик событий OnChange, когда Value изменяется программно. Если два редактора должны отображать одни и те же данные в двух форматах, и вы используете соответствующие обработчики событий OnChange для их синхронизации, это может быть причиной.
  • Возможно, вы напрямую вызываете один обработчик событий из другого.

Способы отладки:

  • Сначала вы должны попробовать решение для точки останова, предложенное paulsm4. Если переполнение стека происходит каждый раз, когда вызывается один из обработчиков OnChange, это решение легко сработает.
  • Закомментируйте код одного из обработчиков событий. Запустите программу, ошибка больше не должна появляться. Раскомментируйте код маленькими (но логичными) частями, протестируйте и повторите. Когда ошибка появляется снова, вы знаете, что финансируете линию, вызвавшую ошибку. Если вы не можете понять это самостоятельно, отредактируйте вопрос, добавьте код и отметьте строку, которая, как вы только что обнаружили, вызывает у вас проблемы.

Если используемые вами элементы управления запускают обработчик событий OnChange при программном изменении значения, вы должны сделать свои обработчики событий нереентерабельными: это точно остановит бесконечный рекурсивный цикл. Я почти всегда предполагаю, что элементы управления вызывают OnChange или эквивалентные события, когда свойства изменяются из кода, и всегда защищаю себя от повторного входа, используя что-то вроде этого:

// Somewhere in the private section of your form's class:
FProcessingEventHandler: Boolean;

// This goes in your event handler
procedure TYourForm.EventHandler(Sender:TObject);
begin
  if FProcessingEventHandler then Exit; // makes code non-reentrant
  FProcessingEventHandler := True;
  try
    // old code goes here ...
  finally FProcessingEventHandler := False;
  end;
end;
person Cosmin Prund    schedule 08.03.2012
comment
Большое спасибо, приведенный выше код сработал, и теперь нет ошибки переполнения стека. - person ; 08.03.2012

Предложения:

  1. Установите точку останова в EditChange и OldCustomEditChange, чтобы увидеть, кто их вызывает. Каждый вызов. Очевидно, что только EditChange должен всегда вызывать OldCustomEditChange.

  2. Посмотрите в своем .dfm, чтобы убедиться, что EditChange назначается только одному событию (а не нескольким событиям), а OldCustomEditChange вообще не назначается.

person paulsm4    schedule 08.03.2012

Вы сообщаете о непрерывной последовательности рекурсивных вызовов EditChange. Глядя на код EditChange, есть два кандидата на рекурсивный вызов:

  1. OldCustomEditChange равен EditChange или вызывает функцию, которая, в свою очередь, вызывает EditChange.
  2. Обработчик событий, реагирующий на изменения в FValueControl.Font вызовом EditChange.

Это единственные возможности для кода в EditChange вызывать самого себя.

Легко увидеть, как обе эти возможности приводят к незавершенному рекурсивному вызову функции и, в конечном счете, к переполнению стека. Из двух кандидатов моя ставка номер 1. Я бы внимательно изучил, что происходит, когда коллируют OldCustomEditChange.

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

person David Heffernan    schedule 08.03.2012