[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