Доступ к переменной в родительской форме из события OnTimer — получение исключения

Я получаю исключение в обработчике событий OnTimer (TTimer), который при выполнении увеличивает целочисленную переменную в родительской форме. Таймеры должны иметь доступ к увеличенному целому числу, используемому в качестве идентификатора.

Мой первый вопрос: как я могу определить в Delphi 2007, какой код выполняется в каком потоке? Есть ли способ в режиме отладки проверить это, чтобы я мог точно определить?

Во-вторых, если мне нужно получить доступ и изменить переменные в родительской форме из другого потока, как лучше всего это сделать? Кажется, что иногда Delphi позволяет мне обращаться к этим переменным "неправильно", не выдавая исключения, а иногда выдает исключение.


person Dave    schedule 31.01.2009    source источник
comment
Не могли бы вы немного уточнить, какие исключения вы получаете? Можете ли вы показать код?   -  person Harriv    schedule 01.02.2009
comment
У вас есть два вопроса, поэтому задавайте их отдельно. Вы получите более качественные и целенаправленные ответы, и у вас будет возможность получить вдвое больше очков репутации.   -  person Rob Kennedy    schedule 02.02.2009


Ответы (4)


Просто чтобы быть уверенным: с одной стороны, вы говорите о событии таймера, а с другой - о многопоточности. Это два совершенно разных способа параллельного выполнения кода.

Таймер всегда будет запускаться в основном потоке. Там должен быть безопасный доступ ко всему, что было создано и используется в основном потоке. На самом деле событие таймера может произойти только тогда, когда не выполняется никакой другой код основного потока, потому что для обработки сообщения таймера требуется обработчик сообщений приложения. Так что это либо вне какого-либо кода обработки событий, либо когда один из ваших обработчиков событий вызывает Application.ProcessMessages.

Нить сильно отличается от этого. В этом случае код в разных потоках выполняется независимо друг от друга. При работе на многопроцессорной машине (или многоядерной) возможно даже, что они действительно работают параллельно. Таким образом, у вас может возникнуть довольно много проблем, в частности, Delphi VCL (включая Delphi XE) не сохраняет потоки, поэтому вызовы любого класса VCL должны выполняться только из основного потока (есть несколько исключений для это правило).

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

person dummzeuch    schedule 01.02.2009

Как в Delphi 2007 определить, какой код выполняется в каком потоке? Есть ли способ в режиме отладки проверить это, чтобы я мог точно определить?

Вы можете установить точку останова, и когда выполнение остановится, посмотрите на окно отладки потоков. Дважды щелкните каждый поток, чтобы увидеть его стек вызовов в окне отладки стека вызовов. Вы также можете использовать функцию Win32 GetCurrentThreadId, чтобы узнать о текущем потоке (например, для ведения журнала или для определения, является ли текущий поток основным потоком и т. д.).

Поскольку вы не показываете никакого кода, трудно быть более конкретным. Просто чтобы быть уверенным: код в обработчике событий таймера не выполняется в другом потоке. У вас не будет проблем с одновременным доступом, если вы используете только таймеры, а не настоящие фоновые потоки.

Во-вторых, если мне нужно получить доступ и изменить переменные в родительской форме из другого потока, как лучше всего это сделать? Кажется, что иногда Delphi позволяет мне обращаться к этим переменным "неправильно", не выдавая исключения, а иногда выдает исключение.

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

person Werner Lehmann    schedule 01.02.2009

Вы задаете два вопроса, поэтому я отвечу на них двумя ответами.

Ваш первый вопрос касается использования TTimers; они всегда выполняются в основном потоке.

Скорее всего, ваше исключение является нарушением прав доступа.

Если это так, это обычно вызвано одним из следующих:

  • a- ваша родительская форма уже уничтожена, когда ваш TTimer срабатывает.
  • b- у вас еще нет ссылки на родительскую форму, когда срабатывает ваш TTimer.

b легко: просто проверьте, равна ли ваша ссылка нолю.

a сложнее и зависит от того, как вы ссылаетесь на родительскую форму.

В основном вы хотите убедиться, что ваша ссылка становится нулевой, когда родитель уничтожается или удаляется.

Если вы ссылаетесь на свою родительскую форму через глобальную переменную (в этом примере через Form2), то вы должны заставить TForm2 сделать переменную Form2 nil, используя событие OnDestroy следующим образом:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm2 = class(TForm)
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.FormDestroy(Sender: TObject);
begin
  Form2 := nil;
end;

end.

Если вы используете ссылку на поле родительской формы (например, FMyForm2Reference), вам следует добавить метод уведомления следующим образом:

unit Unit1;

interface

 uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Unit2;

 type
  TForm1 = class(TForm)
  private
    FMyForm2Reference: TForm2;
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
  end;

 var
  Form1: TForm1;

 implementation

{$R *.dfm}

 procedure TForm1.Notification(AComponent: TComponent; Operation: TOperation);
 begin
  inherited Notification(AComponent, Operation);
  if (Operation = opRemove) then
    if (AComponent = FMyForm2Reference) then
      FMyForm2Reference := nil;
 end;

 end.

С уважением,

Йерун Плаймерс

person Jeroen Wiert Pluimers    schedule 01.02.2009

Вы задаете два вопроса, поэтому я отвечу на них двумя ответами.

Ваш второй вопрос касается обеспечения того, чтобы только 1 поток одновременно обращался к 1 переменной в форме.

Поскольку переменная находится в форме, лучше всего использовать для этого метод Synchronize.

Есть отличный пример этого, который поставляется с Delphi, он находится в проекте thrddemo.dpr, где модуль в SortThds.pas имеет этот метод, который показывает, как используй это:

procedure TSortThread.VisualSwap(A, B, I, J: Integer);
 begin
  FA := A;
  FB := B;
  FI := I;
  FJ := J;
  Synchronize(DoVisualSwap);
 end;

Удачи,

Йерун Плаймерс

person Jeroen Wiert Pluimers    schedule 01.02.2009
comment
Можем ли мы уже прекратить советовать людям использовать Synchronize? Во всяком случае, thrddemo.dpr — отличный пример того, как не стоит заниматься многопоточным программированием. Подробный список того, что можно и чего нельзя делать, см. в группах новостей. .cryer.info/borland/public.delphi.internet.winsock/ - person mghie; 01.02.2009