我在 OnTimer 事件处理程序 (TTimer) 中遇到异常,该异常在执行时会增加父表单中的整数变量。计时器需要能够访问用作 id 的递增整数。
我的第一个问题是:如何在 Delphi 2007 中判断哪些代码在哪个线程中运行?有没有办法在调试模式下检查这个,所以我可以确定?
其次,如果我需要从另一个线程访问和修改父表单中的变量,那么最好的方法是什么?似乎有时 Delphi 允许我“错误地”访问这些变量而不给出异常,而其他时候它确实给出了异常。
我在 OnTimer 事件处理程序 (TTimer) 中遇到异常,该异常在执行时会增加父表单中的整数变量。计时器需要能够访问用作 id 的递增整数。
我的第一个问题是:如何在 Delphi 2007 中判断哪些代码在哪个线程中运行?有没有办法在调试模式下检查这个,所以我可以确定?
其次,如果我需要从另一个线程访问和修改父表单中的变量,那么最好的方法是什么?似乎有时 Delphi 允许我“错误地”访问这些变量而不给出异常,而其他时候它确实给出了异常。
只是为了确定:一方面你在谈论一个计时器事件,另一方面是关于多线程。这是两种完全不同的并行运行代码的方式。
计时器将始终在主线程中运行。在那里访问创建并在主线程中使用的所有内容应该是安全的。事实上,定时器事件只有在没有其他主线程代码运行时才会发生,因为它需要应用程序的消息处理程序来处理定时器消息。因此,它要么在任何事件处理代码之外,要么在您的事件处理程序之一调用 Application.ProcessMessages 时。
线程与此非常不同。在这种情况下,不同线程中的代码彼此独立运行。如果在多处理器机器(或多核)上运行,它们甚至有可能真正并行运行。这样你可能会遇到很多问题,特别是 Delphi VCL(包括 Delphi XE)不是线程保存,所以对任何 VCL 类的调用只能从主线程完成(有一些例外情况本规则)。
因此,在期待任何有用的答案之前,请先澄清您是在谈论计时器还是真正的多线程。
如何在 Delphi 2007 中判断哪些代码在哪个线程中运行?有没有办法在调试模式下检查这个,所以我可以确定?
您可以设置断点,并在执行停止时查看线程调试窗口。双击每个线程以在调用堆栈调试窗口中查看其调用堆栈。您还可以使用 Win32 函数 GetCurrentThreadId 来了解当前线程(例如,用于记录,或确定当前线程是否是主线程等)。
由于您没有显示任何代码,因此很难更具体。只是为了确定:计时器事件处理程序中的代码不会在不同的线程中执行。如果您只使用计时器,而不是真正的后台线程,您将不会遇到并发访问问题。
其次,如果我需要从另一个线程访问和修改父表单中的变量,那么最好的方法是什么?似乎有时 Delphi 允许我“错误地”访问这些变量而不给出异常,而其他时候它确实给出了异常。
如果你真的在另一个线程中并访问一个共享变量,如果你不保护该访问,你会看到各种各样的事情发生。它可能在大多数情况下都可以正常工作,或者你会得到奇怪的值。如果您只想以线程安全的方式修改整数,请查看 InterlockedIncrement。否则,您可以使用临界区、互斥体、监视器... JEDI 在 JclSynch 单元中有一些有用的类。
你在问两个问题,所以我会分两个答案来回答。
您的第一个问题是关于使用 TTimers;那些总是在主线程中运行。
最有可能的是,您的异常是访问冲突。
如果是,通常是由以下任一原因引起的:
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.
问候,
杰伦·普莱默斯
你在问两个问题,所以我会分两个答案来回答。
您的第二个问题是关于确保一次只有 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;
祝你好运,
杰伦·普莱默斯