我无法理解它是如何工作的。
首先是一个非常简单的例子,尝试更好地解释我的情况。 此代码位于新项目中创建的Form Form1中。其中mmo1是备忘录组件。
TOb = class
Name : String;
constructor Create(Name : String);
procedure Go();
end;
procedure TOb.Go;
begin
Form1.mmo1.Lines.Add(Name);
end;
然后我有一个关于此事件的按钮:
procedure TForm1.btn4Click(Sender: TObject);
var
Index : Integer;
begin
mmo1.Lines.Clear;
for Index := 1 to 3 do
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(Index)).Go).Start;
end;
备忘录上的输出是:
线程4
线程4
线程4
我真的没有。
第一个问题:为什么“名称”输出是:线程4?是For循环从1到3.至少应该是1或3
第二:为什么它只执行最后一个线程“线程4”,而不是按顺序执行3次“线程1”,“线程2”,“线程3”?
为什么我问这个?我有一个对象,已经有一个正常的进程。但是现在我发现我的情况是需要处理这个对象的List。确实工作很好,一个接一个,但在我的情况下,他们是独立的,所以我想“嗯,让我们把它们放在线程中,这样它会跑得更快”。
为了避免修改对象以扩展 TThread 并覆盖执行,我会查看如何使用过程而不是从TThread继承的对象执行线程匿名线程。使用一个对象可以很好地工作,但是当我尝试遍历我的对象列表时,会发生奇怪的行为。
这具有相同的效果。
for Index := 1 to 3 do
TThread.CreateAnonymousThread(
procedure
var
Ob : TOb;
begin
OB := TOb.Create('Thread ' + IntToStr(Index));
OB.Go;
end
).Start;
当然我不清理对象,这只是我正在运行的一些测试。 有任何想法吗?或者在这种情况下,我需要继承 TThread 并覆盖执行方法?
有趣的是这个运行得很好。
mmo1.Lines.Clear;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(1)).Go).Start;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(2)).Go).Start;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(3)).Go).Start;
输出:
线程1
线程2
线程3
答案 0 :(得分:0)
使用一个对象可以很好地工作,但是当我尝试遍历我的对象列表时,会发生奇怪的行为。
您可能没有考虑how anonymous procedures bind to variables。特别是:
请注意,变量捕获捕获变量 - 而非值。如果变量的值在通过构造匿名方法捕获后发生更改,则捕获的匿名方法变量的值也会发生变化,因为它们是具有相同存储的相同变量。捕获的变量存储在堆上,而不是堆栈中。
例如,如果您执行以下操作:
var
Index: Integer;
begin
for Index := 0 to ObjList.Count-1 do
TThread.CreateAnonymousThread(TOb(ObjList[Index]).Go).Start;
end;
你实际上会在线程中引起EListError
异常(至少当我测试它时 - 我不知道它为什么会发生。通过在调用{之前为线程分配OnTerminate
处理程序来验证{ {1}},然后让该处理程序检查Start()
属性。)
如果你这样做:
TThread(Sender).FatalException
线程不会再崩溃,但它们可能会在同一个var
Index: Integer;
Ob: TOb;
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
TThread.CreateAnonymousThread(Ob.Go).Start;
end;
end;
对象上运行,因为TOb
正在引用CreateAnonymousThread()
方法本身,然后是循环在每次迭代时修改引用的TOb.Go()
指针。我怀疑编译器可能会生成类似于此的代码:
Self
如果你这样做,它将有类似的问题:
var
Index: Integer;
Ob: TOb;
Proc: TProc; // <-- silently added
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
Proc := Ob.Go; // <-- silently added
TThread.CreateAnonymousThread(Proc).Start;
end;
end;
可能因为编译器会生成与此类似的代码:
procedure StartThread(Proc: TProc);
begin
TThread.CreateAnonymousThread(Proc).Start;
end;
...
var
Index: Integer;
Ob: TOb;
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
StartThread(Ob.Go);
end;
end;
这样可以正常工作:
procedure StartThread(Proc: TProc);
begin
TThread.CreateAnonymousThread(Proc).Start;
end;
...
var
Index: Integer;
Ob: TOb;
Proc: TProc; // <--
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
Proc := Ob.Go; // <--
StartThread(Proc);
end;
end;
通过将对procedure StartThread(Ob: TOb);
begin
TThread.CreateAnonymousThread(Ob.Go).Start;
end;
...
var
Index: Integer;
Ob: TOb;
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
StartThread(Ob);
// or just: StartThread(TOb(ObjList[Index]));
end;
end;
的调用移动到将CreateAnonymousThread()
的实际引用隔离为局部变量的单独过程,可以消除捕获多个对象的引用时发生冲突的可能性。
匿名程序很有趣。你必须小心它们如何捕获变量。
答案 1 :(得分:0)
在评论中阅读<{3}} Remy Lebeau 帖子后,我找到了这个解决方案。
通过添加另一个进行调用的过程来更改主对象。 更改循环而不是在主循环中创建匿名线程,它是在对象内创建的。
TOb = class
Name : String;
constructor Create(Name : String);
procedure Process();
procedure DoWork();
end;
procedure TOb.Process;
begin
TThread.CreateAnonymousThread(DoWork).Start;
end;
procedure TOb.DoWork;
var
List : TStringList;
begin
List := TStringList.Create;
List.Add('I am ' + Name);
List.Add(DateTimeToStr(Now));
List.SaveToFile('D:\file_' + Name + '.txt');
List.Free;
end;
循环:
List := TObjectList<TOb>.Create();
List.Add(TOb.Create('Thread_A'));
List.Add(TOb.Create('Thread_B'));
List.Add(TOb.Create('Thread_C'));
List.Add(TOb.Create('Thread_D'));
for Obj in List do
//TThread.CreateAnonymousThread(Obj.Go).Start;
Obj.Process;
只需对主要对象进行最小的更改即可解决问题。
答案 2 :(得分:0)
关于种族状况。当您将最大值增加到100时,将看到不同的值。线程无法保证线程何时开始或结束。 您可以尝试此代码块。
for I := 1 to 100 do
begin
TThread.CreateAnonymousThread(
procedure
var
Msg : string;
begin
try
Msg := 'This' + I.ToString;
MessageDlg(Msg,mtCustom,
[mbYes,mbAll,mbCancel], 0);
Except
on E: Exception do
End;
end
).Start;
end;
如果要保证写入1到4,则应实例化每个值,然后再发送给Thread。
for I := 1 to 100 do
begin
TThread.CreateAnonymousThread(
procedure
var
Msg : string;
begin
var instanceValue := I;
try
Msg := 'This' + instanceValue.ToString;
MessageDlg(Msg,mtCustom,
[mbYes,mbAll,mbCancel], 0);
Except
on E: Exception do
End;
end
).Start;
end;