C# 并行调整图像大小导致内存不足异常
我正在将大量图像并行调整为1000x1000个缩略图,并且很快就会耗尽内存。(Performance profiler让我在大约3分钟后拥有3GB的已用内存) 最初我使用的是C# 并行调整图像大小导致内存不足异常,c#,.net,out-of-memory,gdi+,parallel.foreach,C#,.net,Out Of Memory,Gdi+,Parallel.foreach,我正在将大量图像并行调整为1000x1000个缩略图,并且很快就会耗尽内存。(Performance profiler让我在大约3分钟后拥有3GB的已用内存) 最初我使用的是Image.FromFile(),但是做了一些研究,我发现Image.fromfstream()是一个不错的选择。我认为我有适当的using语句,某些东西仍然保存在内存中,GC没有按预期清除资源 GDI+保持句柄打开似乎有问题,但我似乎找不到适合我的案例的解决方案 问题: 我做错什么了吗 如果没有,有没有更好的方法来处理流/
Image.FromFile()
,但是做了一些研究,我发现Image.fromfstream()
是一个不错的选择。我认为我有适当的using语句,某些东西仍然保存在内存中,GC没有按预期清除资源
GDI+保持句柄打开似乎有问题,但我似乎找不到适合我的案例的解决方案
问题:
Dispose()
,这样我就不会耗尽所有的资源,同时还能保持快速的并行操作列表文件
包含约300个有效JPG图像,每个JPG图像约2-4mb
呼叫者
Execute()
调用Parallel.Foreach()
调整类大小
公共静态类调整大小
{
公共静态void ResizeImage(字符串文件名)
{
调整图像大小(文件名,1000,1000,true);
}
公共静态void ResizeImage(字符串文件名,int-newHeight,int-newWidth,bool-keepasspectratio=true)
{
字符串saveto=Path.GetDirectoryName(文件名)+@“\Alternate\”+Path.GetFileName(文件名);
尝试
{
使用(FileStream fs=newfilestream(文件名,FileMode.Open,FileAccess.Read))
{
使用(Image-ImageFromStream=Image.FromStream(fs))
{
var ImageSize=新尺寸(新高度、新宽度);
if(keepAspectRatio)
{
int oWidth=ImageFromStream.Width;
int oHeight=ImageFromStream.Height;
双pWidth=((双)图像大小.Width/(双)宽度);
双光=((双)图像大小.高度/(双)宽度);
百分之二;
if(高度<宽度)
百分比=pHeight;
其他的
百分比=宽度;
新宽度=(整数)(宽度*百分比);
新高度=(整数)(oHeight*百分比);
}
其他的
{
newWidth=ImageSize.Width;
newHeight=ImageSize.Height;
}
var ResizedImage=新位图(新宽度、新高度);
使用(Graphics gfxHandle=Graphics.FromImage(ResizedImage))
{
gfxHandle.InterpolationMode=InterpolationMode.HighQualityBicubic;
gfxHandle.DrawImage(ImageFromStream,0,0,newWidth,newHeight);
如果(!Directory.Exists(Path.GetDirectoryName(saveto)){Directory.CreateDirectory(Path.GetDirectoryName(saveto));}
ResizedImage.Save(保存到,ImageFormat.Jpeg);
}
ResizedImage.Dispose();
ResizedImage=null;
}
}
}
捕获(例外情况除外)
{
WriteLine(string.Format(“异常:{0}”,例如Message));
}
}
并行性的指出我的Parallel.ForEach()
基本上是在创建过多的新任务,因为它正在等待磁盘访问。在大约5分钟的时候,大约在引发异常时,大约有160个线程。降低并行度会限制创建的线程数量,以及在内存中等待完成加载或写入dis的映像数量设置MaxDegreeOfParallelism=2
似乎是网络磁盘访问的最佳选择,它将线程数减少到25个左右,并将CPU利用率提高到35%左右(从17%提高到24%,这是由于GC阻塞线程,以及太多线程造成的CPU开销)
谢谢@ZacFaragher。我确实记得听说GDI+没有正确释放资源,但那是很久以前的事了,所以我对细节略知一二。你可以做的是使用GC.Collect()强制执行垃圾收集循环
,或者使用显式工作线程限制并行大小调整的数量。或者两者都可以。@ZacFaragher AGC.Collect()
在这种情况下不会起任何作用。我正在观看性能分析器,垃圾工过来捡东西,但它不在路边。不过,关于并行大小调整的数量,您是对的,您刚刚为我指出了解决方案,谢谢。做得好,做得好!今天我了解到parallel.ForEach()
存在!
public void Execute()
{
Parallel.ForEach(Files, (file) =>
{
Resize.ResizeImage(file.FullName);
}
);
}
public static class Resize
{
public static void ResizeImage(string fileName)
{
ResizeImage(fileName, 1000, 1000, true);
}
public static void ResizeImage(string fileName, int newHeight, int newWidth, bool keepAspectRatio = true)
{
string saveto = Path.GetDirectoryName(fileName) + @"\Alternate\" + Path.GetFileName(fileName);
try
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
using (Image ImageFromStream = Image.FromStream(fs))
{
var ImageSize = new Size(newHeight, newWidth);
if (keepAspectRatio)
{
int oWidth = ImageFromStream.Width;
int oHeight = ImageFromStream.Height;
double pWidth = ((double)ImageSize.Width / (double)oWidth);
double pHeight = ((double)ImageSize.Height / (double)oWidth);
double percent;
if (pHeight < pWidth)
percent = pHeight;
else
percent = pWidth;
newWidth = (int)(oWidth * percent);
newHeight = (int)(oHeight * percent);
}
else
{
newWidth = ImageSize.Width;
newHeight = ImageSize.Height;
}
var ResizedImage = new Bitmap(newWidth, newHeight);
using (Graphics gfxHandle = Graphics.FromImage(ResizedImage))
{
gfxHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
gfxHandle.DrawImage(ImageFromStream, 0, 0, newWidth, newHeight);
if (!Directory.Exists(Path.GetDirectoryName(saveto))) { Directory.CreateDirectory(Path.GetDirectoryName(saveto)); }
ResizedImage.Save(saveto, ImageFormat.Jpeg);
}
ResizedImage.Dispose();
ResizedImage = null;
}
}
}
catch (Exception ex)
{
Debug.WriteLine(string.Format("Exception: {0}", ex.Message));
}
}
public void Execute()
{
//Parallel.ForEach(Files, (file) =>
//{
// Resize.ResizeImage(file.FullName);
//}
//);
Parallel.ForEach(Files, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (file) => { Resize.ResizeImage(file.FullName); } );
}