从OnTimer事件访问父窗体中的变量 - 获取异常

时间:2009-01-31 22:51:09

标签: multithreading delphi delphi-2007

我在OnTimer事件处理程序(TTimer)中遇到异常,当执行时会增加父表单中的整数变量。定时器需要能够访问用作id的递增整数。

我的第一个问题是:如何在Delphi 2007中告诉哪个代码在哪个线程中运行?有没有办法在调试模式下检查这个,所以我可以确定?

其次,如果我需要从另一个线程访问和修改父表单中的变量,那么最好的方法是什么?似乎有时Delphi允许我“错误地”访问这些变量而不给出异常,有时候它会给出异常。

4 个答案:

答案 0 :(得分:5)

只是为了确定:一方面,你在谈论一个计时器事件,另一方面是关于多线程。这是两种完全不同的并行运行代码的方式。

计时器将始终在主线程中运行。它应该是安全的,可以访问在主线程中创建和使用的所有内容。实际上,只有当没有其他主线程代码在运行时才会发生计时器事件,因为它需要应用程序的消息处理程序来处理计时器消息。所以它不在任何事件处理代码之外,或者当你的一个事件处理程序调用Application.ProcessMessages时。

线程与此截然不同。在这种情况下,不同线程中的代码彼此独立地运行。如果在多处理器机器(或多核)上运行,甚至可能它们真正并行运行。你可能有这么多问题,特别是Delphi VCL(包括Delphi XE)不是线程保存,所以只能从主线程调用任何VCL类(有几个例外)这个规则)。

因此,在预期任何有用的答案之前,请首先澄清您是在谈论计时器还是真正的多线程。

答案 1 :(得分:3)

  

我如何在Delphi 2007中说出哪些内容   代码在哪个线程中运行?是   有一种方法在调试模式下进行检查   这样我可以确定吗?

您可以设置断点,并在执行停止时查看线程调试窗口。双击每个线程以在callstack调试窗口中查看其callstack。您还可以使用Win32函数GetCurrentThreadId来查找当前线程(例如,用于记录,或确定当前线程是否是主线程等)。

由于您没有显示任何代码,因此很难更具体。只是为了确定:计时器事件处理程序中的代码不会在另一个线程中执行。如果您只使用计时器而不是真正的后台线程,则不会出现并发访问问题。

  

其次,如果我需要访问和   修改父表单中的变量   另一个线程,最好的方法是什么   要做到这一点?好像有时候   Delphi允许我访问这些   变量“不正确”而没有给出   它有例外,有时也有例外   给出例外。

如果您真的在另一个线程中并访问共享变量,那么如果您不保护该访问,则可以看到发生的各种事情。大多数时候它可能正常工作,或者你得到奇怪的值。如果您只想以线程安全的方式修改整数,请查看InterlockedIncrement。否则你可以使用一个临界区,mutex,monitor ... JEDI在JclSynch单元中有一些有用的类。

答案 2 :(得分:3)

你问两个问题,所以我会用两个答案回答。

你的第一个问题是关于使用TTimers;那些总是在主线程中运行。

最有可能的是,您的例外是违规访问。

如果是,则通常由以下任何一种引起:

  • a-您的父表单已经存在 你的TTimer开火时被摧毁。
  • b-你还没有参考 您的TTimer时的父表单 火灾。

b很简单:只需检查您的参考是否 nil

a更难,取决于您如何引用您的父表单。

基本上,您希望确保在销毁或删除父项时您的引用为nil。

如果您通过全局变量引用父表单(在此示例中通过 Form2 ),那么您应该让TForm2使用OnDestroy事件使Form2变量 nil 这样:

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.

此致

Jeroen Pluimers

答案 3 :(得分:2)

你问两个问题,所以我会用两个答案回答。

您的第二个问题是确保一次只有一个线程访问表单中的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;
祝你好运,

Jeroen Pluimers