Search code examples
multithreadingdelphiomnithreadlibrary

parallel.async, pass parameters thread-safe


Take a look at this (pseudo) code

procedure TestASync;
begin
  var lSomeIntf:=TSomeImplementor.Create as ISomeIntf;
  parallel.ASync(
    procedure
    begin
       sleep(1000); // allow the main thread to finish
       MyThreadedProc(lSomeIntf);
    end
  );
  sleep(100); // will finish before sub-thread
end;

This produces a race condition as I found out the hard way. Because TestASync is already finished before the anonymous method gets the chance of calling MyThreadedPorc, MyThreadedProc is called with a nil interface. (Which is already a lot better then some random value)

Q1: Would this also be the case for simple, non ref-counted variables (like an integer, double etc) - I suspect strongly they may get changed/return random values since they are located on the stack.

Q2: How do I get around this in a simple, clean way?

I have been fiddling with the IOmniTask interface and other methods to start an unmonitored background thread stuff like that, but all of these appear to clutter up my code and make the source hard to understand.

Maybe something like this? :

procedure TestGenericASync;
begin
  var lSomeIntf:=TSomeImplementor.Create as ISomeIntf;
  parallel.ASync<ISomeIntf>(lSomeIntf,
    procedure (const aSomeIntf:ISomeIntf)
    begin
       MyThreadedProc(aSomeIntf);
    end
  );
end;



Solution

  • As Dalija suggested, you have to work around a Delphi bug (RSP-26666) with inline variable declarations. These inline variable declarations are not captured properly by anonymous methods.

    The workaround is not to use an inline variable. (or to upgrade to RS 10.4 Sydney.)

    procedure TestASync;
    var lSomeIntf:ISomeIntf; // declaring here works around the bug
    begin
      lSomeIntf:=TSomeImplementor.Create as ISomeIntf;
      parallel.ASync(
        procedure
        begin
           sleep(1000); // allow the main thread to finish
           MyThreadedProc(lSomeIntf);
        end
      );
      sleep(100); // will finish before sub-thread
    end;