Как отправить / получить TObjectList с помощью DataSnap, решая проблему утечки памяти?

Прототип клиентского приложения и сервера DataSnap. Я хочу передать TObjectList с сервера клиенту.

Это работает, но все объекты, которые я передаю, остаются в памяти на сервере и клиенте.

Что я делаю неправильно?

Жизненный цикл = Сессия

Объект TPessoa реализует TObjectList другого класса (TConta):

   TConta = class(TObject)
  private
    FBanco: string;
    FConta: Integer;
    FAgencia: Integer;
    procedure SetAgencia(const Value: Integer);
    procedure SetBanco(const Value: string);
    procedure SetConta(const Value: Integer);
   published
     property Banco : string read FBanco write SetBanco;
     property Agencia : Integer read FAgencia write SetAgencia;
     property Conta : Integer read FConta write SetConta;
   end;

   TContasCollection = TObjectList<TConta>;

   TPessoa = class(TObject)
  private
    FContas: TContasCollection;
    FId: Integer;
    FNome: string;
    procedure SetContas(const Value: TContasCollection);
    procedure SetId(const Value: Integer);
    procedure SetNome(const Value: string);
   published
     property Id : Integer read FId write SetId;
     property Nome : string read FNome write SetNome;
     property Contas : TContasCollection read FContas write SetContas;
   end;

Метод public в ServerMetodsUnit:

function getPessoa(id : Integer) : Tpessoa;

function TServerMethods1.getPessoa(id: Integer): Tpessoa;
begin
  result := Tpessoa.create;
  result.id := id;
  result.nome := 'NoName';
  result.contas := getContas;
end;

function TServerMethods1.getContas: TContasCollection;
var conta : TConta;
begin
  conta := TConta.Create;
  conta.Banco := 'CEF';
  conta.Agencia := 1;
  conta.Conta := 123;

  Result := TContasCollection.Create();
  Result.Add(conta);
end;

Клиент:

procedure TForm2.btn1Click(Sender: TObject);
var pessoa : Tpessoa;
begin
  pessoa := ClientModule1.ServerMethods1Client.getPessoa(1);
  mmo1.Lines.Add(pessoa.Nome);
  mmo1.Lines.Add(pessoa.Contas[0].Banco);
end;

правильный результат, однако сообщение об утечке памяти отображается на сервере и клиенте (System.ReportMemoryLeaksOnShutdown:= true;):

Неожиданная утечка памяти Произошла неожиданная утечка памяти. Неожиданные небольшие утечки блоков:

1 - 12 байт: TMoveArrayManager x 1, неизвестный x 1 13 - 20 байт: TConta x 1, UnicodeString x 1 37 - 44 байта: TObjectList x 1

как решить эту утечку памяти, чтобы не повлиять на сервис?

1 ответ

Утечка памяти происходит из-за того, что вы не освобождаете создаваемые объекты.

Для запуска в клиентской части вы создаете pessoa объект в btn1Click как локальная переменная, но вы не освобождаете ее.

procedure TForm2.btn1Click(Sender: TObject);
var pessoa : Tpessoa;
begin
  pessoa := ClientModule1.ServerMethods1Client.getPessoa(1);
  mmo1.Lines.Add(pessoa.Nome);
  mmo1.Lines.Add(pessoa.Contas[0].Banco);
  pessoa.Free; // you are no longer using pessoa object after that point so release it
end;

TPessoa класс имеет FContas поле, которое мы не видим, как создается или выпускается, возможно, вы только что пропустили код, а может, его вообще нет. Тем не мение, FContas также был выпущен в какой-то момент. Если TPessoa класс является владельцем FContas поле (и, похоже, так и должно быть), то вам придется добавить деструктор TPessoa класс, где вы бы освободить FContas коллекция.

FContas является TObjectList что по умолчанию имеет добавленные объекты, и они будут освобождены, когда FContas выпущен.

Также возможна утечка FContas объект в SetContas метод, но не зная, как этот код выглядит, трудно сказать, утечка или нет.

TPessoa = class(TObject)
....
public
  destructor Destroy; override;
end;

destructor TPessoa.Destroy; 
begin
  FContas.Free;
  inherited;
end;

procedure SetContas(const Value: TContasCollection);
begin
  FContas.Free; // release old FContas collection if there is one
  FContas := Value; // reference FContas grabs ownership here 
end;

По сути, каждый объект, который вы создаете, вы должны освободить после того, как он вам больше не нужен, если только не существует какого-либо другого экземпляра объекта, который захватит владение этим объектом и сделает это за вас, например TObjectList делает.

Выше код предполагает, что FContas Поле является владельцем коллекции, которая в нем хранится, и важно, чтобы вы не захватывали эту ссылку и не держали ее вне срока действия объекта-владельца (TPessoa) пример.

Например, следующий код будет неправильным:

procedure TForm2.btn1Click(Sender: TObject);
var 
  pessoa : Tpessoa;
  contas: TContasCollection;
begin
  pessoa := ClientModule1.ServerMethods1Client.getPessoa(1);
  contas := pessoa.Contas;
  pessoa.Free; // <-- after that point contas points to released object
  mmo1.Lines.Add(contas[0].Banco); // <-- dangling pointer use
end;

Существуют также экземпляры объектов с подсчетом ссылок, которые автоматически освобождаются после того, как последняя ссылка на них выходит из области видимости (обычно потомки TInterfacedObject класс), но управление ими - совсем другая история. Вы не используете их здесь, и я упоминаю их только для полноты.

Другие вопросы по тегам