如何在OTL中使用匿名方法时捕获变量? [英] How can I capture variables by anonymous method when using it in OTL?

查看:188
本文介绍了如何在OTL中使用匿名方法时捕获变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想做什么:



我在genric列表中有几个对象。我想以匿名方法捕获每个对象,并作为单独的OTL任务执行此方法。



这是一个简化的示例:

 程序Project51; 

{$ APPTYPE CONSOLE}

使用
SysUtils,Generics.Collections,OtlTask​​Control,OtlTask​​;

type
TProc =程序引用;

type
TMyObject = class(TObject)
public
ID:Integer;
constructor Create(AID:Integer);
end;

构造函数TMyObject.Create(AID:Integer);
begin
ID:= AID;
end;

var
对象:TList< TMyObject> ;;
LObject:TMyObject;
MyProc:TProc;
begin
Objects:= TList< TMyObject> .Create;
Objects.Add(TMyObject.Create(1));
Objects.Add(TMyObject.Create(2));
Objects.Add(TMyObject.Create(3));
for LObject in Objects do
begin
//这似乎工作
MyProc:= procedure
begin
Writeln(Format('[SameThread] Object ID:%d',[LObject.ID]));
end;
MyProc;
//这不工作,有时它在控制台返回4行!
CreateTask(
procedure(const Task:IOmniTask)
begin
Writeln(Format('[Thread%d] Object ID:%d',[Task.UniqueID,LObject。 ID]));
end
).Unobserved.Run;
end;
睡眠(500); //等待一个任务完成
Readln;
end。

这是结果:





如您所见,捕获似乎在主线程中工作良好。但是,我不知道一个指向一个对象的指针是否已被捕获或只有它的ID字段?



当我尝试捕获对象并将匿名方法传递给 CreateTask 函数的东西变得奇怪。



首先,只有 TMyObject 似乎被捕获。第二,我有四个消息在控制台日志,尽管事实,我只有三个对象在通用列表中。第二种行为是不一致的,有时我在控制台中有三条消息,有时我有四条消息。



请解释我上面提到的两个问题的原因,一个解决方案,消除了问题,并允许我将对象的每个实例传递到一个单独的OTL任务。 (我不想使用常规 TThread 类。)

解决方案

本文档介绍了正在发生的事情


请注意,变量捕获会捕获变量 - 不是。如果变量的值在通过构造匿名方法捕获后发生变化,那么匿名方法捕获的变量的值也会改变,因为它们是具有相同存储的同一个变量。


在您的代码中,只有一个 LObject 变量,因此全部您构造的匿名方法引用它。随着循环的进行, LObject 的值会发生变化。任务还没有得到开始运行的机会,所以当它们终于运行时,循环已经终止,并且 LObject 具有其最终值。

要捕获循环变量的,请将创建的任务包装在一个单独的函数:

  function CreateItemTask(Obj:TMyObject):TOmniTaskDelegate; 
begin
结果:= procedure(const Task:IOmniTask)
begin
Writeln(Format('[Thread%d] Object ID:%d',[Task.UniqueID, Obj.ID]));
end;
end;

然后更改您的循环代码:

  CreateTask(CreateItemTask(LObject))。Unobserved.Run; 


What I want to do:

I have a few objects in a genric list. I want to capture each of this object in anonymous method and execute this method as a separate OTL Task.

This is a simplified example:

program Project51;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections, OtlTaskControl, OtlTask;

type
  TProc = reference to procedure;

type
  TMyObject = class(TObject)
  public
    ID: Integer;
  constructor Create(AID: Integer);
  end;

constructor TMyObject.Create(AID: Integer);
begin
  ID := AID;
end;

var
  Objects: TList<TMyObject>;
  LObject: TMyObject;
  MyProc: TProc;
begin
  Objects := TList<TMyObject>.Create;
  Objects.Add(TMyObject.Create(1));
  Objects.Add(TMyObject.Create(2));
  Objects.Add(TMyObject.Create(3));
  for LObject in Objects do
  begin
    //This seems to work
    MyProc := procedure
    begin
      Writeln(Format('[SameThread] Object ID: %d',[LObject.ID]));
    end;
    MyProc;
    //This doesn't work, sometimes it returns 4 lines in console!?
    CreateTask(
      procedure(const Task: IOmniTask)
      begin
        Writeln(Format('[Thread %d] Object ID: %d',[Task.UniqueID, LObject.ID]));
      end
    ).Unobserved.Run;
  end;
  Sleep(500); //Just wait a bit for tasks to finish
  Readln;
end.

And this is the result:

As you can see, capturing seems to work fine in the main thread. However, I do not know if a pointer to an object has been captured or only its ID field?

When I try to capture the object and pass the anonymous method to CreateTask function things become weird.

First of all, only the third instance of TMyObject seemed to be captured. Second of all, I've got four messages in console log despite the fact that I have only three objects in generic list. The second behaviour is inconsistent, sometimes I've got three messages in console, sometimes I've got four.

Please explain me the reason for two issues mentioned above and propose a solution that eliminates the problem and allows me to pass each instance of object to a separate OTL task. (I do not want to use regular TThread class.)

解决方案

The documentation describes what's happening:

Note that variable capture captures variables—not values. If a variable's value changes after being captured by constructing an anonymous method, the value of the variable the anonymous method captured changes too, because they are the same variable with the same storage.

In your code, there is only one LObject variable, so all the anonymous methods you construct refer to it. As your loop makes progress, the value of LObject changes. The tasks haven't gotten a chance to start running yet, so when they do finally run, the loop has terminated and LObject has its final value. Formally, that final value is undefined after the loop.

To capture the value of the loop variable, wrap creation of the task in a separate function:

function CreateItemTask(Obj: TMyObject): TOmniTaskDelegate;
begin
  Result := procedure(const Task: IOmniTask)
            begin
              Writeln(Format('[Thread %d] Object ID: %d',[Task.UniqueID, Obj.ID]));
            end;
end;

Then change your loop code:

CreateTask(CreateItemTask(LObject)).Unobserved.Run;

这篇关于如何在OTL中使用匿名方法时捕获变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆