How do I pass a context inside a loop into a TTask.IFuture in Delphi?

别说谁变了你拦得住时间么 提交于 2021-01-28 12:44:36

问题


I have a set of tasks to perform they are in an array. I want to loop through the array and launch a task for each.

The following example is trivial (calculating squares) to demonstrate the issue.

program FutureSquares;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Threading,
  System.Classes;
const
  nums: array[0..9] of Integer = (1,2,3,4,5,6,7,8,9,10);
var
  i, contextIndex: Integer;
  f: array[0..9] of ITask;
  answer, futureAnswer: Integer;
  matchDisplay: string;
  futureFunc: TFunc<Integer>;
begin

  try
    for i := Low(f) to High(f) do
    begin
      contextIndex := i;

      futureFunc := function: Integer
        begin
          Sleep(Random(1000));
          Result := nums[contextIndex]*nums[contextIndex]; // trying  because a reference to it is captured. How to capture the actual value?
        end;

      f[i] := TTask.Future<Integer>(futureFunc);
      f[i].Start;
    end;

    //verify results in sequential manner
    for i := Low(f) to High(f) do
    begin
      answer := nums[i]*nums[i];
      futureAnswer := IFuture<Integer>(f[i]).Value;
      if futureAnswer = answer then
        matchDisplay := 'match'
      else
        matchDisplay := 'MISMATCH';
      writeln(Format('%d * %d = %d. Future<Integer> = %d - %s', [nums[i],nums[i],answer, futureAnswer, matchDisplay]));
    end;

    readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

The output from the program is as follows:

1 * 1 = 1. Future<Integer> = 16 - MISMATCH
2 * 2 = 4. Future<Integer> = 100 - MISMATCH
3 * 3 = 9. Future<Integer> = 100 - MISMATCH
4 * 4 = 16. Future<Integer> = 100 - MISMATCH
5 * 5 = 25. Future<Integer> = 100 - MISMATCH
6 * 6 = 36. Future<Integer> = 100 - MISMATCH
7 * 7 = 49. Future<Integer> = 100 - MISMATCH
8 * 8 = 64. Future<Integer> = 100 - MISMATCH
9 * 9 = 81. Future<Integer> = 100 - MISMATCH
10 * 10 = 100. Future<Integer> = 100 - match

How can I achieve my goal?

I note that TTask.Future does not allow me to pass a context to it which would be useful.


回答1:


You need to capture the value and not the variable in your anonymous method. The easiest way to do this is to put the anonymous method in a standalone function and pass in index that you would like captured. That way, the value will be captured instead of the variable.

function CaptureFuture(const aTheIndex: Integer): TFunc<Integer>;
begin
  Result := function: Integer
    begin
      Sleep(Random(1000));
      Result := nums[aTheIndex]*nums[aTheIndex]; // trying  because a reference to it is captured. How to capture the actual value?
    end;
end;

And then, when you do the assignment of futureFunc, your code would look like this:

for i := Low(f) to High(f) do
begin
  futureFunc := CaptureFuture(i);

  f[i] := TTask.Future<Integer>(futureFunc);
  f[i].Start;
end;

This will give you the desired results:

1 * 1 = 1. Future<Integer> = 1 - match
2 * 2 = 4. Future<Integer> = 4 - match
3 * 3 = 9. Future<Integer> = 9 - match
4 * 4 = 16. Future<Integer> = 16 - match
5 * 5 = 25. Future<Integer> = 25 - match
6 * 6 = 36. Future<Integer> = 36 - match
7 * 7 = 49. Future<Integer> = 49 - match
8 * 8 = 64. Future<Integer> = 64 - match
9 * 9 = 81. Future<Integer> = 81 - match
10 * 10 = 100. Future<Integer> = 100 - match

See this question for more information on variable vs value capturing: Anonymous methods - variable capture versus value capture



来源:https://stackoverflow.com/questions/47706948/how-do-i-pass-a-context-inside-a-loop-into-a-ttask-ifuture-in-delphi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!