Я выполняю задание cron на C#, которое принимает 200 000 изображений и конвертирует их в 1-битное изображение. Во время выполнения этого задания иногда process crashes
(хотя у меня есть глобальная попытка catch), иногда для некоторых изображений (не для всех) он выдает OutOfMemoryException
, а иногда для некоторых изображений он выдает A generic error occurred in GDI
.
int pageSize = 1000;
for (int pageNumber = 0; pageNumber < 200; pageNumber++)
{
var imageUrls = allIMageUrls.Skip(pageSize * pageNumber).Take(pageSize).ToList();
var counter = 0;
var total = imageUrls.Count;
Logger.Log($"Page Number : {pageNumber}");
var failedImageUrls = new System.Collections.Concurrent.ConcurrentBag<string>();
Parallel.ForEach(imageUrls, imageUrl =>
{
try
{
Interlocked.Increment(ref counter);
var image = _httpService.DownloadImage(imageUrl);
if (image != null && image.Length > 0)
{
var oneBitImage = ConvertToOnebitFaxGroup4(contract);
_httpService.UploadImage(image, oneBitImage);
oneBitImage = null;
image = null;
}
}
catch (Exception ex)
{
failedImageUrls.Add(imageUrl);
Logger.Log(ex);
}
});
Это однократный процесс. Я добавил разбиение на страницы, чтобы в случае сбоя я мог перезапустить с этой страницы, а не начинать с начала.
public static class ImageProcessor
{
static ImageCodecInfo _codecInfo;
static EncoderParameters _encoderParameters;
static ImageProcessor()
{
foreach (var codec in ImageCodecInfo.GetImageEncoders())
{
if (codec.MimeType == "image/tiff")
{
_codecInfo = codec;
break;
}
}
_encoderParameters = new EncoderParameters(2);
_encoderParameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
_encoderParameters.Param[1] = new EncoderParameter(Encoder.ColorDepth, (long)1);
}
public static byte[] ConvertToOnebitFaxGroup4(byte[] bytes)
{
using (var memoryStream = new MemoryStream(bytes))
{
var image = Image.FromStream(memoryStream);
var pData = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, pData, bytes.Length);
var bytesPerLine = (image.Width + 31) / 32 * 4;
var img = new Bitmap(image.Width, image.Height, bytesPerLine, PixelFormat.Format1bppIndexed, pData);
using (var ms = new MemoryStream())
{
image.Save(ms, _codecInfo, _encoderParameters);
img.Dispose();
Marshal.FreeHGlobal(pData);
return ms.ToArray();
}
}
}
Обновлено:
public static byte[] ConvertToOnebitFaxGroup4(byte[] bytes)
{
using (var memoryStream = new MemoryStream(bytes))
{
using (var image = Image.FromStream(memoryStream))
{
var pData = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, pData, bytes.Length);
var bytesPerLine = (image.Width + 31) / 32 * 4;
using (var img = new Bitmap(image.Width, image.Height, bytesPerLine, PixelFormat.Format1bppIndexed, pData))
{
using (var ms = new MemoryStream())
{
img.Save(ms, _codecInfo, _encoderParameters);
Marshal.FreeHGlobal(pData);
return ms.ToArray();
}
}
}
}
}
Обновление2
public static byte[] ConvertToOnebitFaxGroup4(byte[] bytes)
{
using (var memoryStream = new MemoryStream(bytes))
{
using (var image = Image.FromStream(memoryStream))
{
using (var ms = new MemoryStream())
{
image.Save(ms, _codecInfo, _encoderParameters);
return ms.ToArray();
}
}
}
}
Вы имеете в виду, что я должен поместить изображение и img (Bitmap) в блоки using или вызвать явное удаление? Этот код был скопирован с использованием stackoverflow.com/questions/26930656/convert-tiff-to-1bit. У вас есть предложения по улучшению приведенного выше кода.
Или вы можете просто вызвать Изображение.Save с целевым потоком и информацией о формате. Как: преобразовать изображение BMP в изображение PNG показывает, что Save
может принимать определенный формат или более детально закодированные параметры кодера.
Изображение, объекты Bitmap имеют, подлежащие удалению. Да, вам нужны блоки using
для всех. Однако большую часть кода можно удалить. Image.Save
может сохранять в целевой поток и использовать другой формат. Вы можете написать image.Save(ms,codecInfo,encoderParameters)
для создания нового образа. Не надо повторно использует переменную image
как для исходного, так и для целевого изображения. Оба они должны быть утилизированы в конце
Кстати, я удаляю растровое изображение. Не могли бы вы ответить на вопрос кодом, который можно упростить.
Я обнаружил ошибку в коде. Думаю стоит img.Save
вместо image.Save(ms
?
Вы можете увидеть обновленный код?
Вам даже не нужен img
, он никогда не использовался. После того, как вы удалите его, ни pdata
, ни какой-либо из Marhal не нужен. код. image
должен быть объявлен в операторе using
, чтобы гарантировать его удаление.
img - это Bitmap, мне нужно было указать 1-битное изображение?
Вы указали, что в параметрах кодировщика
Снова обновлен код. Пожалуйста, проверьте. Спасибо за вашу помощь
Не могли бы вы ответить на это? Чтобы другим было полезно
Просто хочу подтвердить после внесения этого изменения. Больше никаких сбоев, никаких исключений. Вы гениальны. :)
Вы забыли удалить объекты Image и Bitmap. Объекты GDI дороги и ограничены. Еще одна странная вещь - это копирование байтов вроде этого, Marshal.AllocHGlobal, нескольких потоков памяти. Преобразование формата в GDI + выполняется с помощью объектов ImageEncoder и ImageDecorer.