MakeScreenshot утекает?

Всем добрый вечер!

В текущем проекте я испытываю довольно тревожную утечку памяти, которую я просто не могу исправить.

Я оставил приложение работать на ночь при стандартном использовании, и когда я проснулся через 8 часов, он занимал ~ 750 МБ памяти, тогда как начался с ~ 50 МБ. Диспетчер задач Windows не подходит для проверки утечек, кроме как для определения того, что она существует.

Я уже устранил несколько других утечек памяти, основная из которых связана с TGlowEffect Firemonkeys. Он не обнаруживается ReportLeaksOnShutdown, но использование памяти становится чрезмерным для динамически изменяемого объекта (например, при повороте или изменении масштаба).

Я отследил это до таймера (и его отключение полностью останавливает утечку), и мне нужна помощь в его устранении, если это возможно.

Описание: этот код использует функцию Firemonkey MakeScreenshot для сохранения внешнего вида TPanel (SigPanel) в TMemoryStream. Эти данные потока затем загружаются на удаленный FTP-сервер с использованием стандартного кода (см. Ниже). Внутри SigPanel есть 4 TLabel детей, 1 TRectangle ребенок и 6 TImage детей.

Примечания: CfId - это глобальная строка, которая создается на основе случайного extended значения с плавающей запятой, которое затем хешируется вместе с DateTime в формате yyyymmdd_hhnnsszzz. Эта генерация выполняется при создании формы и повторяется до тех пор, пока не получит действительный CfId (т.е. не будет содержать символы, недопустимые для использования в именах файлов Windows). Как только он получает действительный CfId, он больше не запускается (поскольку мне больше не нужно генерировать новый идентификатор). Это позволяет мне почти полностью исключить возможность дублирования CfId.

Код в таймере следующий:

var
  i : Integer;
  SigStream : TMemoryStream;
begin
  SigStream := TMemoryStream.Create;
  SigPanel.MakeScreenshot.SaveToStream(SigStream);
  SigPanel.MakeScreenshot.Free;
  if VT2SigUp.Connected then
  begin
    VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False);
  end else
  begin
    VT2SigUp.Connect;
    VT2SigUp.Put(SigStream,'Sig_'+CfId+'.png',False);
  end;
    SigStream.Free;
end;

Когда таймер НЕ запущен, код работает полностью без утечек, а ReportMemoryLeaksOnShutdown НЕ генерирует сообщение. Когда таймер включен и мне разрешено «работать» хотя бы один раз, я получаю много утечек, которые тем больше, чем больше таймер запускается. Сообщенные утечки следующие:

Small Block Leaks

1 - 12 Bytes: Unknown x 1
13 - 20 Bytes: TList x 5, Unknown x 1
21 - 28 Bytes: TFont x 2, TGradientPoint x 8, TGradientPoints x 4, Unknown x 4
29 - 36 Bytes: TObjectList<FMX.Types.TCanvasSaveState> x 1, TBrushBitmap x 4,
TBrushGrab x 4, TPosition x 24, TGradient x 4, UnicodeString x1
37 - 44 Bytes: TBrushResource x 4
53 - 60 Bytes: TBrush x 4
61 - 68 Bytes: TBitmap x 5
69 - 76 Bytes: TD2DCanvasSaveState x 1
205 - 220 Bytes: TCanvasD2D x 1

Sizes of Medium and Large Block Leaks
200236

По мере запуска таймера эти значения умножаются n раз (n - количество запусков таймера). Средний и большой блоки имеют значение 200236 n (например, если таймер сработал 3 раза, это 200236, 200236, 200326).

Интересно, что если я удалю код, связанный с MakeScreenshot, утечки больше не будет, а использование памяти останется на несколько нормальном уровне. Помимо обычного использования памяти, нет ничего необычного и об утечках не сообщается. Я пробовал несколько образцов кода, как с сохранением в поток, так и с загрузкой оттуда, или с сохранением в поток> Файл, а затем с загрузкой файла, но, похоже, в самой функции есть утечка. Я даже добавил MakeScreenshot.Free, как только обнаружил здесь утечку, но я просто не могу ее заткнуть, и, конечно же, я использовал try..finally в одном из «тестовых прогонов» кода.

Я даже запустил код с GDI + в качестве типа холста, и там произошла такая же утечка (с единственным изменением, что утечка D2D вместо этого ссылается на GDI +).

Я был бы очень признателен за любые исследования или заметки, которые есть у кого-либо по этому поводу, и, более того, за решение проблемы.


person Scott P    schedule 27.05.2012    source источник
comment
Я думаю, вы только что обнаружили утечку памяти в FM (:   -  person    schedule 28.05.2012
comment
Я считаю, что установка ReportMemoryLeaksOnShutdown := True; в инициализации вашего приложения должна помочь, чтобы показать вам, что протекает ...   -  person Jerry Dodge    schedule 28.05.2012
comment
@DorinDuminica Я так считаю. Однако я считаю, что обнаружил, что проблема в том, что Result в FMX.Types.MakeScreenshot на самом деле не освобожден. Они просто звонят Result.Canvas.EndScene и никогда не освобождают его! @JerryDodge Это то, что я побежал выяснить, что именно просачивалось, поскольку без этого я бы не смог получить точный список :)   -  person Scott P    schedule 28.05.2012
comment
Извините, опубликовал это, еще не прочитав всего   -  person Jerry Dodge    schedule 28.05.2012
comment
В настоящее время выполняется проверка того, что освобождение результата является обязательным исправлением и не вызывает всплывающих окон каких-либо странных ошибок. Отправлю ответ через несколько часов. Я полагаю, что это именно то, что нужно, но я хочу дать ему поработать некоторое время, чтобы быть уверенным. Пока результат выглядит многообещающим, поскольку использование памяти остается стабильным.   -  person Scott P    schedule 28.05.2012
comment
@ScottPritchard У меня нет FMX, но вы сможете легко найти виновника с помощью таких инструментов, как EurekaLog, а затем скопировать код из FMX и внести необходимые изменения для предотвращения утечки в специальной функции, может быть, вспомогательной функции класса?   -  person    schedule 28.05.2012


Ответы (1)


Вы не освобождаете растровое изображение, созданное MakeScreenshot.

procedure TForm1.Button1Click(Sender: TObject);
var
  ms: TMemoryStream;
begin
  ms := TMemoryStream.Create;
  Panel1.MakeScreenshot.SaveToStream(ms);
  ms.Free;
end;

В приведенном выше коде нет ссылки на созданное растровое изображение, поэтому нет возможности его освободить. Вместо этого измените свой дизайн, как показано ниже:

procedure TForm1.Button2Click(Sender: TObject);
var
  ms: TMemoryStream;
  bmp: TBitmap;
begin
  ms := TMemoryStream.Create;
  bmp := Panel1.MakeScreenshot;
  bmp.SaveToStream(ms);
  ms.Free;
  bmp.Free;
end;


С помощью приведенного ниже кода вы фактически создаете два растровых изображения и освобождаете одно из них.

  SigPanel.MakeScreenshot.SaveToStream(SigStream);
  SigPanel.MakeScreenshot.Free;


В итоге ваш код будет больше похож на следующий:

var
  i : Integer;
  Bmp: TBitmap;
  SigStream : TMemoryStream;
begin
  SigStream := TMemoryStream.Create;
  try
    Bmp := SigPanel.MakeScreenshot;
    try
      Bmp.SaveToStream(SigStream);
      if not VT2SigUp.Connected then
        VT2SigUp.Connect;
      VT2SigUp.Put(SigStream, 'Sig_'+CfId+'.png', False);
    finally
      Bmp.Free;
    end;
  finally
    SigStream.Free;
  end;
end;
person Sertac Akyuz    schedule 28.05.2012
comment
Дополняя ответ @Sertac, не забудьте использовать try, наконец, чтобы избежать ошибок при установке и избежать утечки потока памяти, поскольку SigStream.Free не будет вызываться при возникновении какого-либо исключения. - person Diego Garcia; 28.05.2012
comment
Ах, конечно, вот и все. Это кажется ужасно запутанным способом решения этой проблемы, и поначалу казалось, что результат не был освобожден в FMX.Types. Я предполагал, что MakeScreenshot нужно вызвать и дать ссылку (например, поток), но видя, как вы это сделали, я понимаю, как функция предназначена для правильного использования. Как я уже сказал, я использовал try..finally в одном из своих исправлений кода, но взял его, чтобы попытаться максимально упростить код, чтобы исправить проблему. - person Scott P; 28.05.2012
comment
@Scott - Ничего страшного, не обращая внимания на обработку ошибок, когда вы публикуете здесь вопрос или делаете первоначальный дизайн. Я просто не хотел упускать это из виду, когда увидел, что это упоминается в комментариях. - person Sertac Akyuz; 28.05.2012