WriteableBitmap или утечка памяти в программе записи PNG?
Я создаю небольшое приложение для Windows Phone 8 (христианский православный календарь), в котором есть фоновый агент, который должен обновлять живую плитку. Приложению потребуется доступ к контактам в телефоне, поэтому я отказался от доступа к Интернету, чтобы не возникало вопроса о создании внутренних блоков. Я лично не доверял бы приложению, которое имеет доступ к моим контактам и интернету.
Недавно мой запланированный агент (который генерирует три PNG) запустил OutOfMemoryException. Последовательно. Я использовал DeviceStatus для запроса и отладки его поведения.
Трудно назвать это утечкой памяти, поскольку между всеми тремя поколениями плиток, если я вызову GC.Collect, это не вызовет исключение OutOfMemoryException. Если бы это была настоящая утечка памяти, на некоторые (большие и / или многие) объекты могли бы ссылаться другие живые / корневые объекты, и никакое количество GC.Collect не поможет. В моем случае GC.Collect поможет. Я могу продолжать использовать GC.Collect, но чувствую себя грязно, делая это.
Поскольку я разрабатываю бесплатное приложение с открытым исходным кодом, вы можете подробно просмотреть весь код проекта в текущем состоянии разработки на http://orthodoxcalendar.codeplex.com/SourceControl/latest
Генерация плитки состоит из взятия фона и наложения двух других изображений на этот фон. В основном для каждого из трех сгенерированных PNG я делаю
var bytes1 = (byte[])resourceManager.GetObject(resourceName1);
var stream1 = new MemoryStream(bytes);
var bytes2 = (byte[])resourceManager.GetObject(resourceName2);
var stream2 = new MemoryStream(bytes);
var bytes3 = (byte[])resourceManager.GetObject(resourceName3);
var stream3 = new MemoryStream(bytes);
var writeableBitmap1 = BitmapFactory.New(size.Width, size.Height).FromStream(stream1); // background
var writeableBitmap2 = BitmapFactory.New(size.Width, size.Height).FromStream(stream2); // first overlay
var writeableBitmap3 = BitmapFactory.New(size.Width, size.Height).FromStream(stream3); // second overlay
writeableBitmap1.Blit(new Point(0, 0), writeableBitmap2, new Rect(0, 0, width2, height2), Colors.White, BlendMode.Alpha);
writeableBitmap1.Blit(new Point(0, 0), writeableBitmap3, new Rect(0, 0, width3, height3), Colors.White, BlendMode.Alpha);
writeableBitmap1.DrawText("Some text", new Point(5, 139), Color.Black, 17);
writeableBitmap1.Invalidate(); // flatten things
using(var outputStream = new WhateverStream())
{
PNGWriter.Write(writeableBitmap1, outputStream);
}
writeableBitmap1.SetSource(new MemoryStream(MiscData.MinimumPng)); // set the writeable bitmap to a 1x1 transparent PNG to, hopefully, force it to release unamanaged memory or other stuff
writeableBitmap2.SetSource(new MemoryStream(MiscData.MinimumPng));
writeableBitmap3.SetSource(new MemoryStream(MiscData.MinimumPng));
stream1.Dispose();
stream2.Dispose();
stream3.Dispose();
Код, если вы посмотрите на проект, не совсем такой, как описанный выше, так как я обернул почти все зависимости в адаптерах и извлеченных интерфейсах. Через много сборок. Приведенный выше код является упрощенной версией, которая просто показывает, как я считаю, соответствующие строки кода.
Несколько пояснений к коду выше:
- весь этот код выполняется в фоновом агенте внутри Dispatcher.BeginInvoke, так как вы не можете манипулировать WritableBitmap в любом другом потоке, кроме потока пользовательского интерфейса
- Данные PNG хранятся в другой сборке как resx. Я знаю, что это расширяет сборку, но мне нужно это для повторного использования на разных платформах, так как сборка представляет собой PCL
- Создание WriteableBitmap напрямую с использованием байтового массива кажется загадочным образом неудачным, поэтому я оборачиваю его в MemoryStream и каким-то образом это работает
- Автор PNG взят из ToolStack.
- Невозможно предварительно сгенерировать изображения, поскольку существует несколько версий "первого наложения", "второго наложения" и, в основном, "некоторого текста". Это будет означать, по крайней мере, десятки тысяч изображений.
Суть вопроса: я делаю что-то ужасно неправильное, о чем я не знаю? Единственное, что мне приходит в голову, это то, что JPEG генерируются быстрее и с меньшим потреблением памяти, но у них не будет прозрачности, которую я желаю. Можно ли это назвать утечкой памяти?
ПОСЛЕДНЕЕ РЕДАКТИРОВАНИЕ: Кажется, что после некоторой дополнительной отладки он изменил свое поведение с приведенного выше на настоящую утечку памяти. Я переключился с поколения PNG на JPEG, и теперь память стала меньше. Входные изображения по-прежнему в формате PNG, но на другом конце JPEG будет плеваться. Объем памяти ушел на несколько мегабайт ниже предыдущего порогового значения.
ВТОРОЕ РЕДАКТИРОВАНИЕ: Я разместил логику в цикле повторения 10.000 на кнопке, и, кажется, потребление памяти не слишком велико. Я начинаю думать, что на самом деле не утечка памяти, а просто более высокое потребление памяти во время генерации, и этого достаточно, чтобы сломать хрупкий агент.
1 ответ
При выполнении аналогичной вещи мне пришлось явно установить для writeablebitmaps значение null (даже если это не требуется) перед вызовом GC.Collect.
Кроме того, может быть лучше создавать и уничтожать (и собирать) каждое из изображений по очереди, а не создавать их все, а затем уничтожать их все. Это поможет с накладными расходами в любой точке.
Также обратите внимание, что при отслеживании использования памяти в отладчике отладчик добавляет около 3 МБ служебных данных, которые вы не увидите при работе.