2

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

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

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

4

4 回答 4

5

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

计时器将始终在主线程中运行。在那里访问创建并在主线程中使用的所有内容应该是安全的。事实上,定时器事件只有在没有其他主线程代码运行时才会发生,因为它需要应用程序的消息处理程序来处理定时器消息。因此,它要么在任何事件处理代码之外,要么在您的事件处理程序之一调用 Application.ProcessMessages 时。

线程与此非常不同。在这种情况下,不同线程中的代码彼此独立运行。如果在多处理器机器(或多核)上运行,它们甚至有可能真正并行运行。这样你可能会遇到很多问题,特别是 Delphi VCL(包括 Delphi XE)不是线程保存,所以对任何 VCL 类的调用只能从主线程完成(有一些例外情况本规则)。

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

于 2009-02-01T09:52:55.407 回答
3

如何在 Delphi 2007 中判断哪些代码在哪个线程中运行?有没有办法在调试模式下检查这个,所以我可以确定?

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

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

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

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

于 2009-02-01T00:01:00.390 回答
3

你在问两个问题,所以我会分两个答案来回答。

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

最有可能的是,您的异常是访问冲突。

如果是,通常是由以下任一原因引起的:

  • a-当您的 TTimer 触发时,您的父表单已经被销毁。
  • b-当您的 TTimer 触发时,您还没有对您的父表单的引用。

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

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

基本上,您要确保当父级被销毁或删除时您的引用为零。

如果您通过全局变量(在本例中通过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),那么您应该使用添加这样的 Notification 方法:

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.

问候,

杰伦·普莱默斯

于 2009-02-01T10:22:00.420 回答
2

你在问两个问题,所以我会分两个答案来回答。

您的第二个问题是关于确保一次只有 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;

祝你好运,

杰伦·普莱默斯

于 2009-02-01T10:26:50.223 回答