Анонимные методы в Delphi создают закрытие, которое сохраняет "окружающие" локальные переменные в контексте, пока анонимный метод не завершится. Если использовать переменные интерфейса, то они уменьшат свой экземпляр ссылок до того, как анонимный метод завершится. Пока все хорошо.
При использовании TTask.Run(AProc: TProc) с анонимным методом я ожидаю, что закрытие будет выпущено, когда связанный рабочий поток завершит выполнение "AProc". Однако этого не происходит. При завершении программы, когда пул потоков (к которому принадлежит этот поток, сгенерированный TTask, принадлежит), вы можете, наконец, увидеть, что эти экземпляры с локальным ссылочным номером освобождаются - т.е. Закрытие становится явно выпущенным.
Вопрос в том, является ли это особенностью или ошибкой? Или я что-то наблюдаю здесь?
Ниже, после TTask.Run(...). wait Я бы ожидал, что деструктор LFoo будет вызван - чего не происходит.
procedure Test3;
var
LFoo: IFoo;
begin
LFoo := TFoo.Create;
TTask.Run(
procedure
begin
Something(LFoo);
end).Wait; // Wait for task to finish
//After TTask.Run has finished, it should let go LFoo out of scope - which it does not apprently.
end;
Ниже приведен полный тестовый пример, который показывает, что "простой" анонимный метод работает как ожидалось (Test2), но при подаче в TTask.Run это не (Test3)
program InterfaceBug;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.Classes,
System.SysUtils,
System.Threading;
type
//Simple Interface/Class
IFoo = interface(IInterface)
['{7B78D718-4BA1-44F2-86CB-DDD05EF2FC56}']
procedure Bar;
end;
TFoo = class(TInterfacedObject, IFoo)
public
constructor Create;
destructor Destroy; override;
procedure Bar;
end;
procedure TFoo.Bar;
begin
Writeln('Foo.Bar');
end;
constructor TFoo.Create;
begin
inherited;
Writeln('Foo.Create');
end;
destructor TFoo.Destroy;
begin
Writeln('Foo.Destroy');
inherited;
end;
procedure Something(const AFoo: IFoo);
begin
Writeln('Something');
AFoo.Bar;
end;
procedure Test1;
var
LFoo: IFoo;
begin
Writeln('Test1...');
LFoo := TFoo.Create;
Something(LFoo);
Writeln('Test1 done.');
//LFoo goes out od scope, and the destructor gets called
end;
procedure Test2;
var
LFoo: IFoo;
LProc: TProc;
begin
Writeln('Test2...');
LFoo := TFoo.Create;
LProc := procedure
begin
Something(LFoo);
end;
LProc();
Writeln('Test2 done.');
//LFoo goes out od scope, and the destructor gets called
end;
procedure Test3;
var
LFoo: IFoo;
begin
Writeln('Test3...');
LFoo := TFoo.Create;
TTask.Run(
procedure
begin
Something(LFoo);
end).Wait; // Wait for task to finish
//LFoo := nil; This would call TFoo destructor,
//but it should get called automatically with LFoo going out of scope - which apparently does not happen!
Writeln('Test3 done.');
end;
begin
try
Test1; //works
Writeln;
Test2; //works
Writeln;
Test3; //fails
Writeln('--------');
Writeln('Expected: Three calls of Foo.Create and three corresponding ones of Foo.Destroy');
Writeln;
Writeln('Actual: The the third Foo.Destroy is missing and is executed when the program terminates, i.e. when the default ThreadPool gets destroyed.');
ReadLn;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.