Предотвращение OutOfMemoryException с помощью GC.AddMemoryPressure()?
В настоящее время я отлаживаю метод, который мы используем для тегов изображений с определенным текстом перед их отображением в нашей системе.
Метод тега выглядит следующим образом:
private static Image TagAsProductImage(Image image)
{
try
{
// Prepares the garbage collector for added memory pressure (500000 bytes is roughly 485 kilobytes).
// Should solve some OutOfMemoryExceptions.
GC.AddMemoryPressure(500000);
using (Graphics graphics = Graphics.FromImage(image))
{
// Create font.
Font drawFont = new Font("Tahoma", image.Width*IMAGE_TAG_SIZE_FACTOR);
// Create brush.
SolidBrush drawBrush = new SolidBrush(Color.Black);
// Create rectangle for drawing.
RectangleF drawRect = new RectangleF(0, image.Height - drawFont.GetHeight(), image.Width,
drawFont.GetHeight());
// Set format of string to be right-aligned.
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Far;
// Draw string to screen.
graphics.DrawString(TAG_TEXT, drawFont, drawBrush, drawRect, drawFormat);
}
}
// If an out of memory exception is thrown, return the unaltered image.
catch(OutOfMemoryException)
{
GC.RemoveMemoryPressure(500000);
return image;
}
GC.RemoveMemoryPressure(500000);
return image;
}
Вставить вещи в контекст: этот метод вызывается после того, как изображение было извлечено с нашего сервера изображений и сохранено в локальном кеше (что наша система использует совместно с другими системами, для которых нужны одни и те же изображения).
У нас были проблемы с OutOfMemoryExceptions
при достижении using (Graphics...
(когда изображение должно быть извлечено с сервера до отметки, если изображение существует в кеше, тегирование не было проблемой).
Чтобы предотвратить/обойти исключение OutOfMemoryException, я пробовал три разных подхода, и, хотя они работают, мне действительно не нравится ни одна из них.
Сначала я попытался сделать общий GC.Collect();
перед вызовом Graphics.FromImage(image)
, который работал (конечно), но мне не нравится принудительно собирать, так как он сильно ударил по производительности.
Мой второй подход заключался в вызове GC.Collect()
в catch-statement, а затем рекурсивном вызове TagAsProductImage(image)
, но это может привести к бесконечному циклу, если GC не освободит достаточное количество памяти.
И, наконец, я закончил с приведенным выше кодом, и я не могу сказать, что я тоже люблю.
Возможно, мне удастся использовать GC.Collect()
, поскольку вся работа по получению изображения из тега обслуживания → сохранение → довольно велика, поэтому производительность, получаемая от сбора, будет минимальной, но я бы действительно как лучшее решение.
Если у кого-то есть разумное решение, делитесь им.
Ответы
Ответ 1
Если вы ищете способ убедиться, что у вас достаточно памяти для операции, используйте MemoryFailPoint
.
При этом через using
вы можете определить область, в которой вам понадобится определенный объем памяти. Если он недоступен, он выкинет восстанавливаемый InsufficientMemoryException
.
Подробнее см. http://msdn.microsoft.com/en-us/library/system.runtime.memoryfailpoint.aspx.
Ответ 2
У вас другая проблема, этот код использует очень мало памяти. К сожалению, исключения GDI + довольно сложны. Диагностируйте это с помощью TaskMgr.exe, вкладки Процессы. View + Select Columns и отметьте объекты GDI, дескрипторы и объекты USER.
Если мое подозрение верное, вы увидите счетчик объектов GDI, чтобы ваш процесс постоянно поднимался по мере запуска этого кода. Когда он достигает 10 000 Windows, решает, что в коде есть что-то принципиально неправильное и отказывается создавать какие-либо ручки. GDI + затем немного понемногу об этом и сообщает об ошибке в памяти. Неправильно, это должно было быть ошибкой "не удалось создать дескриптор". Код ошибки, которого у него нет..NET неспособен улучшить исключение.
Anyhoo, причина в том, что вы не вызываете Dispose() на шрифт и кисть. Оберните их с помощью инструкции using. Обычно это не вызывает проблем, но ваша программа, по-видимому, использует слишком мало собранной памяти, чтобы когда-либо запускать поток финализатора.