TEvent.SetEvent 上的 TThread 行为

TThread behavior on TEvent.SetEvent

提问人:serhiyiv 提问时间:5/5/2023 更新时间:5/5/2023 访问量:176

问:

使用 TThread 和 TEvent 启动线程完成的工作时,我遇到了奇怪的行为。由于暂停/恢复已被弃用,我一直在寻找使用 TEvent,所以这里只是我拥有的简化代码,但它仍然无法按预期执行。我有带有FThreadStartEvent的TSampleThread类。例如,我创建了 TSampleThread 的 3 个实例,但是当我通过在其中设置 FThreadStartEvent 来启动一个线程时,不知何故,所有实例都会被执行。

enter image description here

问题是什么? 谢谢大家。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Contnrs, System.SyncObjs,
  Vcl.StdCtrls;


type
  TSampleThread = class;
  TTaskProc = procedure(const SampleThread:TSampleThread) of object;

 TSampleThread = class (TTHread)
  public
   FId:String;
   FThreadStartEvent: TEvent;
   FOnThreadExecute:TTaskProc;
   constructor Create;
   destructor Destroy; override;
   procedure  Execute; override;
   procedure  Start;
 end;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private

    { Private declarations }
  public
    task1, task2, task3:TSampleThread;
    procedure onTask(const SampleThread: TSampleThread);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TSampleThread.Execute;
begin
 repeat
  FThreadStartEvent.WaitFor(INFINITE);
  FThreadStartEvent.ResetEvent;
  if Assigned(TMethod(FOnThreadExecute).Code) then FOnThreadExecute(Self);
 until terminated;

end;


procedure TSampleThread.Start;
begin
 FThreadStartEvent.SetEvent;
end;

constructor TSampleThread.Create;
begin
  FreeOnTerminate:= False;
  FOnThreadExecute:= nil;
  FThreadStartEvent:= TEvent.Create(nil, true, false, 'ThreadStartEvent');
  inherited Create(false);

end;

destructor TSampleThread.Destroy;
begin
   FThreadStartEvent.SetEvent;
   FThreadStartEvent.Free;
   inherited;
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
 task1.Start;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin

  task1:= TSampleThread.Create;
  task1.FId:= 'Task 1' ;
  task1.FOnThreadExecute:= onTask;


  task2:= TSampleThread.Create;
  task2.FId:= 'Task 2' ;
  task2.FOnThreadExecute:= onTask;

  task3:= TSampleThread.Create;
  task3.FId:= 'Task 3' ;
  task3.FOnThreadExecute:= onTask;


end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
 task1.Free;
 task2.Free;
 task3.Free;
end;

procedure TForm1.onTask(const SampleThread: TSampleThread);
begin
  SampleThread.Synchronize(nil,
  procedure
  begin
    Memo1.Lines.Add(SampleThread.FId);
  end);
end;




end.
多线程 Delphi 事件 tthread

评论


答:

3赞 Remy Lebeau 5/5/2023 #1

在这种情况下,请勿为对象指定名称:TEvent

https://docwiki.embarcadero.com/Libraries/en/System.SyncObjs.TEvent.Create

设置“名称”可为新事件对象提供名称或指定现有的命名事件对象。如果没有其他线程或进程需要访问事件对象来等待其信号,则可以将 Name 留空。Name 可以是最多 260 个字符的字符串,不包括反斜杠字符 ()。如果 Name 用于指定现有事件对象,则该值必须与区分大小写的比较中现有事件的名称匹配。如果 Name 与现有信号量、互斥锁或文件映射对象的名称匹配,则将创建 TEvent 对象,并将 Handle 设置为 0,并且所有方法调用都将失败。

所有对象都分配了相同的名称,因此这会导致所有线程在 Windows 内核中共享一个命名事件对象,从而发出信号,表明任何线程都将满足所有线程的等待。TEvent

此外,如果另一个应用中的线程决定访问同一命名事件对象,则命名事件会使您的应用受到外部干扰。

您需要更改此设置:

FThreadStartEvent:= TEvent.Create(nil, true, false, 'ThreadStartEvent');

取而代之的是:

FThreadStartEvent := TEvent.Create(nil, true, false, '');

或者更简单:

FThreadStartEvent := TEvent.Create;


注意:您没有说您使用的是哪个版本的 Delphi。您的代码在子句中使用了单位范围名称,因此它必须至少为 XE2。但是,在 XE8 之前,构造函数中存在一个错误,导致即使参数为空,它仍会创建命名事件对象。此问题已在 XE8 中修复。对于早期版本,请直接使用 Win32 CreateEvent() 函数。usesTEventName

评论

1赞 AmigoJack 5/5/2023
...或根据 CreateEventA() 为每个事件使用不同的名称。对于每个事件,您仍然会获得不同的句柄,但在内部它们都是相同的,这是故意的。
2赞 Remy Lebeau 5/5/2023
@AmigoJack是的,使用不同的名称可以解决问题(但不会消除外部干扰),但是根本没有充分的理由为一开始就不打算共享的内核对象分配名称。
0赞 serhiyiv 5/5/2023
Remy Lebeau:我正在使用 Delphi 11。一切正常。谢谢大家。