Embarcadero's documentation covers this in detail:
Anonymous Methods in Delphi: Variable Binding Mechanism
If an anonymous method refers to an outer local variable in its body, that variable is "captured". Capturing means extending the lifetime of the variable, so that it lives as long as the anonymous method value, rather than dying with its declaring routine. 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. Captured variables are stored on the heap, not the stack.
The documentation then goes into greater detail explaining exactly how the implementation captures variables, especially when multiple anonymous methods capture the same variable (as is the case in your example).
This situation is more complicated in the case of multiple anonymous methods capturing the same local variable. To understand how this works in all situations, it is necessary to be more precise about the mechanics of the implementation.
(details follow...)
So, your issue has nothing to do with TTask/PPL itself, and everything to do with the fact that you are creating multiple anonymous procedures that share the j
variable. They are capturing a reference to the variable itself, not its value. By the time all of the tasks have finished sleeping, j
has been set to its final value, which all of the tasks then output.
The fix (which is also described in the same documentation above) is to have your loop pass the variable as a parameter to an intermediate function which then declares the anonymous method to capture the parameter. That way, each anonymous method is capturing a different variable, eg:
procedure LaunchTask(index: Integer);
var
ltask: ITask;
begin
ltask := TTask.Create(
procedure ()
begin
Sleep(3000);
SendDebugFmt('Task #%d' , [index]);
end);
ltask.Start;
end;
procedure TForm2.LaunchTasks;
const
cmax = 5;
Var
i: Integer;
begin
for i := 1 to cmax do
begin
LaunchTask(i);
end;
end;
An alternative solution is to replace your loop with TParallel.For()
instead of using TTask
directly, eg:
procedure TForm2.LaunchTasks;
const
cmax = 5;
begin
TParallel.&For(1, cmax,
procedure (index: integer)
begin
Sleep(3000);
SendDebugFmt('Task #%d' , [index]);
end);
end;