大家好,我正在制作一个C#WinForms应用程序,该应用程序在目录中搜索重复的图像。首先从目录中的每个图像调用构造函数。
目录中有很多文件,内存迅速上升到2gb,然后程序将抛出内存不足异常。
现在,我在for循环中添加了一个检查,以检查内存是否已超过800兆位,并强制执行垃圾回收。但是我注意到在第一次强制收集之后,内存不再增加。 (强制垃圾回收发生在〜800中的〜180循环中,然后再也不会发生)
我为为什么会发生这种情况而感到困惑,因此来到这里寻求帮助。
private void GeneratePrints()
{
for (int i = 0; i < files.Count; i++)
{
if (imageFileExtensions.Contains(Path.GetExtension(files[i])))
prints.Add(new FilePrint(directory + "/" + files[i]));
//800 Megabits
const long MAX_GARBAGE = 800 * 125000;
if (GC.GetTotalMemory(false) > MAX_GARBAGE)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Console.WriteLine("File Prints Complete.");
}
一旦选择目录,GeneratePrints()将被调用1次。
我还将向您展示FilePrint类的构造函数。 我很确定这一切与MemoryStream对象有关。
public FilePrint(string filename)
{
Bitmap img;
using (var fs = new FileStream(filename, FileMode.Open))
{
using (var ms = new MemoryStream())
{
fs.CopyTo(ms);
ms.Position = 0;
img = (Bitmap)Bitmap.FromStream(ms);
ms.Close();
}
fs.Close();
}
this.size = img.Size;
img = ResizeImage(img, 8, 8);
img = MakeGrayscale(img);
//I do some basic for-loop arithmetic here
//calculating the average color of the image, not worth posting.
img.Dispose();
}
所以基本上我想知道如何做到这一点,以免一开始就不会出现“类似鲨鱼鳍”的内存使用高峰,因此我不必强制进行垃圾收集。
这是发生强制垃圾回收时拍摄的内存快照(我没有正确处理MemoryStreams吗?):
感谢您的答复,想法和答案!
答案 0 :(得分:1)
您没有显示方法ResizeImage(img, 8, 8)
和MakeGrayscale(img)
,但是很可能它们只是基于旧方法创建并返回新图像。如果是这样,您的代码会构造两个Bitmap
对象,但从未明确对其进行处置,因此请尝试处置它们,例如如下:
using (var old = img)
img = ResizeImage(old, 8, 8);
using (var old = img)
img = MakeGrayscale(old);
您可能还想保证使用img
处理最后的try/finally
:
Bitmap img = null;
try
{
img = new Bitmap(filename); // Here I simplified the code, but this will leave the file locked until `img` is disposed after resizing.
this.size = img.Size;
using (var old = img)
img = ResizeImage(old, 8, 8);
using (var old = img)
img = MakeGrayscale(old);
//I do some basic for-loop arithmetic here
//calculating the average color of the image, not worth posting.
}
finally
{
if (img != null)
img.Dispose();
}
您可能会长时间使用大量内存,然后急剧下降,这可能是最终最终确定了未处理映像的非托管资源,但是,因为GC并未意识到由所有者拥有的非托管内存未处置的Bitmap
对象,不一定插入,将位图标识为未引用,并在相当长的一段时间内将其传递给finalizer thread。很难预测终结器线程何时启动甚至开始工作。请参见 Are .net finalizers always executed? ,答案不一定是 。但是,致电GC.WaitForPendingFinalizers();
可能会启动该过程。
顺便说一句,MemoryStream
实际上不需要 放置在当前实现中,因为它缺少不受管理的资源。参见this answer的Jon Skeet到 Is a memory leak created if a MemoryStream in .NET is not closed? 进行确认。 (这样做仍然是一个好习惯,尽管在Bitmap
的情况下,讨厌的文件/流锁定使它不可能。)