[fpc-pascal] Function reference doesn't capture in thread
Sven Barth
pascaldragon at googlemail.com
Sat Sep 17 17:51:43 CEST 2022
Am 17.09.2022 um 17:04 schrieb Hairy Pixels:
>
>> On Sep 17, 2022, at 9:43 PM, Sven Barth <pascaldragon at googlemail.com> wrote:
>>
>> Then you didn't read my announcement mail deeply enough, cause it's mentioned there!
> Yes I just read that over and I remember it now. I feel like this not the same behavior in other languages although I’d need to test. Most recently I’m using Swift for my day job and I don’t recall ever having confusions like this come up.
Complain to the developers of Delphi then.
I do however plan to extend anonymous (and nested) functions with a
syntax that allows to select whether a variable should be by-reference
or by-value. But that's still a bit off, cause I have other things to do
first.
>
>> And they are not pointers, but the variable is moved to an object instance which implements the interface of the function reference.
> “Moved to an object” makes me think the variable is copied but the reference behavior you describe sounds like a pointer so I’m still not 100% clear on what you mean.
Let's take your initial example and adjust it so that i will really be
captured:
=== code begin ===
{$mode objfpc}
{$modeswitch anonymousfunctions}
{$modeswitch functionreferences}
{$modeswitch arrayoperators}
program test;
uses
Cthreads, SysUtils, Classes;
type
TProc = reference to procedure;
TCallback = class(TThread)
private
proc: TProc;
public
constructor Create(p: TProc);
procedure Execute; override;
end;
procedure TCallback.Execute;
begin
proc();
Sleep(100);
end;
constructor TCallback.Create(p: TProc);
begin
inherited Create(true);
proc := p;
end;
procedure DoTest;
var
i: integer;
callback: TCallback;
callbacks: array of TCallback = ();
begin
for i := 1 to 4 do
begin
callback := TCallback.Create(procedure
begin
writeln('Invoked: ', i, ' id: ',
HexStr(TThread.CurrentThread));
end);
callback.Start;
callbacks += [callback];
end;
for i := 0 to High(callbacks) do
callbacks[i].WaitFor;
end;
begin
DoTest;
end.
=== code end ===
What the compiler will make of it will be as follows (the only
difference will be inside DoTest):
=== code begin ===
procedure DoTest;
type
TCapturer = class(TInterfacedObject, TProc)
i: integer;
procedure Anonymous1;
procedure TProc.Invoke = Anonymous1;
end;
procedure TCapturer.Anonymous1;
begin
writeln('Invoked: ', i, ' id: ', HexStr(TThread.CurrentThread));
end;
var
capturer: TCapturer;
capturer_keepalive: IUnknown;
callback: TCallback;
callbacks: array of TCallback = ();
begin
capturer := TCapturer.Create;
capturer_keepalive := capturer;
for capturer.i := 1 to 4 do
begin
callback := TCallback.Create(capturer as TProc);
callback.Start;
callbacks += [callback];
end;
for capturer.i := 0 to High(callbacks) do
callbacks[capturer.i].WaitFor;
end;
=== code end ===
That should make it clearer what is happening behind the scenes.
>>
>> Even so I’m trying now to modifying the example by copying to a local variable but I still see this same behavior suggesting there isn’t actually a copy. Why is this?
>>
>> When your callback is executed i might already have been changed!
> Ok that makes sense now since it’s just a reference. So this would have worked if I would have passed in i as a parameter and then stored it locally? I’m thinking now how I can preserve a copy of the state in the scope for each thread and I think local vars are the only way, correct? We need some more thread based examples to help people understand I think because I’m finding all sorts of edge cases updating a little thread library myself.
You need to move the creation of the thread to a separate nested
function (cause each thread will then have a separate capture object;
also this will work no matter if this is done in the main block or
inside a procedure/function/method):
=== code begin ===
function CreateCallback(aIndex: Integer): TCallback;
begin
Result := TCallback.Create(procedure
begin
writeln('Invoked: ', aIndex, ' id: ',
HexStr(TThread.CurrentThread));
end);
end;
var
i: integer;
callback: TCallback;
callbacks: array of TCallback = ();
begin
for i := 1 to 4 do
begin
callback := CreateCallback(i);
callback.Start;
callbacks += [callback];
end;
for i := 0 to High(callbacks) do
callbacks[i].WaitFor;
end.
=== code end ===
Regards,
Sven
More information about the fpc-pascal
mailing list