TTask / TThreadPool may keep interfaced objects alive

UPDATE: The issue described here has been marked „resolved“ on May, 5th 2017, so we should see corrected behaviour in one of the next deliveries.

I am using TTask.Run quite a lot in projects and in many of my demos. I won’t go into the details of how to use TTask here, but I only want to highlight that there is still an open issue, which may cause interfaced objects to be destroyed much later, than you would expect. Ultimately this may lead to a much higher memory footprint of your application.

As of Delphi 10.1 Update 2 the issue is still marked open on quality.emabaracdero.com – you might vote for it, if your code is affected by it.

Interfaced objects use automatic reference counting for lifecycle management:

var
  LFoo : IFoo
begin
  LFoo := TFoo.Create;
  //work with LFoo
end;  //LFoo goes out of scope, its reference counter will go to zero,
      // thus it will be destroyed here automatically.

With TTask.Run you can start background threads, that will execute code passed in as anonymous method:

var
  LFoo : IFoo
begin
  LFoo := TFoo.Create;
  TTask.Run(procedure
    begin
      Foobar(LFoo); //Work with LFoo 
    end;
  ).Waitfor; //Wait for task/thread to be completed 
end;  //LFoo goes out of scope, its reference counter SHOULD go to zero,
      // thus it SHOULD be destroyed here automatically.   

Unfortunately the above does not work as expected. The internals of TTask (actually TThreadpool) keep a reference to the (already finished) task and prevent the instance of TFoo, which is referenced by LFoo, to be destroyed automatically. At least when the TThreadPool gets destroyed, all those kept references will be released and the instances will be destroyed, thus they won’t report as memory leak (with ReportMemoryLeaksOnShutDown).

In other words: you will have to set LFoo to nil manually – until this issue gets solved in one of the next updates.

  TTask.Run(procedure
    begin
      Foobar(LFoo); //Work with LFoo 
    end;
  ).Waitfor; //Wait for task/thread to be completed 
  LFoo := nil; //Depending on your logic, this could also be done inside TTask.Run
end;    

2 Comments

  • Mark

    20. März 2017 at 4:39

    I found this post looking for a solution to a problem I’m running into. I have a simple FireMonkey app where I do one call to TTask.Run(…)

    The Task.Run makes one call to a SOAP web service.

    On shut down the app does not close gracefully and hangs up until I get a „Not Responding“ message and I have to close it via the task manager.

    If I comment out that line the app and it doesn’t execute, the app closes gracefully.

    Have you seen this?

    Thanks

    • Olaf Monien

      29. März 2017 at 15:15

      Did you check if your SOAP service call ever returns? Does you app close gracefully if you comment out the SOAP call (only the actual SOAP call, not the TTask.Run)

TTask / TThreadPool may keep interfaced objects alive

UPDATE: The issue described here has been marked „resolved“ on May, 5th 2017, so we should see corrected behaviour in one of the next deliveries.

I am using TTask.Run quite a lot in projects and in many of my demos. I won’t go into the details of how to use TTask here, but I only want to highlight that there is still an open issue, which may cause interfaced objects to be destroyed much later, than you would expect. Ultimately this may lead to a much higher memory footprint of your application.

As of Delphi 10.1 Update 2 the issue is still marked open on quality.emabaracdero.com – you might vote for it, if your code is affected by it.

Interfaced objects use automatic reference counting for lifecycle management:

var
  LFoo : IFoo
begin
  LFoo := TFoo.Create;
  //work with LFoo
end;  //LFoo goes out of scope, its reference counter will go to zero,
      // thus it will be destroyed here automatically.

With TTask.Run you can start background threads, that will execute code passed in as anonymous method:

var
  LFoo : IFoo
begin
  LFoo := TFoo.Create;
  TTask.Run(procedure
    begin
      Foobar(LFoo); //Work with LFoo 
    end;
  ).Waitfor; //Wait for task/thread to be completed 
end;  //LFoo goes out of scope, its reference counter SHOULD go to zero,
      // thus it SHOULD be destroyed here automatically.   

Unfortunately the above does not work as expected. The internals of TTask (actually TThreadpool) keep a reference to the (already finished) task and prevent the instance of TFoo, which is referenced by LFoo, to be destroyed automatically. At least when the TThreadPool gets destroyed, all those kept references will be released and the instances will be destroyed, thus they won’t report as memory leak (with ReportMemoryLeaksOnShutDown).

In other words: you will have to set LFoo to nil manually – until this issue gets solved in one of the next updates.

  TTask.Run(procedure
    begin
      Foobar(LFoo); //Work with LFoo 
    end;
  ).Waitfor; //Wait for task/thread to be completed 
  LFoo := nil; //Depending on your logic, this could also be done inside TTask.Run
end;    

2 Comments

  • KMorwath

    6. März 2017 at 9:57

    Does it mean it will happen with any ARC implementation too? It may be going to have a not little impact on the new Linux code, as soon as you use such implementation.

    • Olaf Monien

      6. März 2017 at 12:04

      As the actual issue is not related to the implementation of Interfaces, but lies in Threadpool implementation it will probably affect ARC (Linux and mobile platforms that is) as well.
      I’ll run some checks asap.

Wir benutzen Cookies um die Nutzerfreundlichkeit der Webseite zu verbessen. Durch Deinen Besuch stimmst Du dem zu.