Переполнение стека Delphi из-за цикла обработки событий - PullRequest
1 голос
/ 08 марта 2012

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

enter image description here

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

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

EDIT

..

Основная форма имеет таймер, который обновляет все элементы управления 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

EDIT2

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

редактировать 3 Значение стека вызовов, но что такое "???" enter image description here

РЕДАКТИРОВАТЬ 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;

Ответы [ 3 ]

9 голосов
/ 08 марта 2012

Исходя из размещенного стека вызовов, очевидно, почему происходит ошибка: 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;
3 голосов
/ 08 марта 2012

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

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

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

3 голосов
/ 08 марта 2012

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

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

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

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

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

...