提问人:serhiyiv 提问时间:5/5/2023 更新时间:5/5/2023 访问量:176
TEvent.SetEvent 上的 TThread 行为
TThread behavior on TEvent.SetEvent
问:
使用 TThread 和 TEvent 启动线程完成的工作时,我遇到了奇怪的行为。由于暂停/恢复已被弃用,我一直在寻找使用 TEvent,所以这里只是我拥有的简化代码,但它仍然无法按预期执行。我有带有FThreadStartEvent的TSampleThread类。例如,我创建了 TSampleThread 的 3 个实例,但是当我通过在其中设置 FThreadStartEvent 来启动一个线程时,不知何故,所有实例都会被执行。
问题是什么? 谢谢大家。
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.
答:
在这种情况下,请勿为对象指定名称: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()
函数。uses
TEvent
Name
评论
CreateEventA()
为每个事件使用不同的名称。对于每个事件,您仍然会获得不同的句柄,但在内部它们都是相同的,这是故意的。
评论