【问题标题】:Why does Image.FromFile keep a file handle open sometimes?为什么 Image.FromFile 有时会保持文件句柄打开?
【发布时间】:2010-10-21 18:15:16
【问题描述】:

我在 ASP.NET 应用程序中的 .NET 中使用 GDI+ 进行大量图像处理。

我经常发现 Image.FromFile() 使文件句柄保持打开状态。

这是为什么?在不保留文件句柄的情况下打开图像的最佳方法是什么。

  • 注意:我没有做任何愚蠢的事情,比如让 Image 对象到处乱放 - 即使我这样做了,我也不希望文件句柄保持活动状态

【问题讨论】:

  • 您确定 FromFile 正在这样做吗?傻,我知道,但您可以使用句柄(SysInternal 实用程序)来验证句柄确实来自 FromFile。

标签: gdi+ filehandle


【解决方案1】:

我经历了与此线程上的其他几张海报相同的旅程。我注意到的事情:

  1. 使用 Image.FromFile 似乎在释放文件句柄时确实无法预测。 调用 Image.Dispose() 并没有在所有情况下释放文件句柄。

  2. 使用 FileStream 和 Image.FromStream 方法有效,如果您在 FileStream 上调用 Dispose() 或按照 Kris 的建议将整个内容包装在 Using {} 语句中,则会释放文件的句柄。但是,如果您随后尝试将 Image 对象保存到流中,Image.Save 方法将引发异常“GDI+ 中发生一般错误”。大概 Save 方法中的某些内容想了解原始文件。

  3. Steven 的方法对我有用。我能够删除内存中带有 Image 对象的原始文件。我还能够将图像保存到流和文件中(我需要做这两件事)。我还能够保存到与原始文件同名的文件中,如果您使用 Image.FromFile 方法,这是不可能的(我觉得这很奇怪,因为这肯定是最有可能的用例,但是嘿.)

总结一下,像这样打开你的图片:

Image img = Image.FromStream(new MemoryStream(File.ReadAllBytes(path)));

然后,您可以随意操作它(以及原始文件)。

【讨论】:

  • 我在生产中使用这种技术的 webapp 似乎会泄漏内存。我让它在 6 个网络服务器上运行,几天后相关的 ASP.NET 进程正在使用 > 2gb 的内存。我通过将相关的应用程序池设置为在内存使用量达到 500mb 或每天凌晨 2 点后进行回收来进行修复。我目前没有时间对此进行调查,但当我这样做时,我会在此处发布我的解决方案。
  • 您找到/解决了您的问题吗?
  • @Nifle - 遗憾的是没有!我似乎记得在 MSDN 中阅读过,服务/Web 应用程序不支持使用 GDI+ 类,我明白了原因。内存使用率非常高。具有讽刺意味的是,我的部分问题是我已经用一个封装了所有逻辑的数据模型“正确”地完成了一些事情——这使得在使用这些东西之后将其拆解变得更加困难。我们的解决方案是为此功能使用单独的应用程序服务器 - 无论如何,系统都是 SOA,所以对我们来说并没有太大的痛苦。
  • 关于您的第 2 点,可能是因为您使用的是不可搜索的流,这就是内存流起作用的原因。为了保存,您可以尝试(伪代码): a using(filestream) { using(new memorystream) { i.Save(memstream); memstream.WriteTo(fs); } }
  • 创建的三个对象(File、MemoryStream 和 Image)是否需要处理?
【解决方案2】:

我也遇到了同样的问题,于是求助于阅读文件

return Image.FromStream(new MemoryStream(File.ReadAllBytes(fileName)));

【讨论】:

  • 不知道为什么我的回答被否决了。它会在不保持手柄打开的情况下读取。
【解决方案3】:

Image.FromFile 使文件句柄保持打开状态,直到图像被释放。来自MSDN

“在释放图像之前,文件保持锁定状态。”

使用 Image.FromStream,你就不会有问题了。

using(var fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
    return Image.FromStream(fs);
}

编辑:(一年多之后)

上面的代码是危险的,因为它是不可预测的,在某个时间点(关闭文件流之后)你可能得到可怕的“GDI+中发生一般错误”。我将其修改为:

Image tmpImage;
Bitmap returnImage;

using(var fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
    tmpImage = Image.FromStream(fs);
    returnImage = new Bitmap(tmpImage);
    tmpImage.Dispose();
}

return returnImage;

【讨论】:

  • 我觉得 FileStream 也需要清理一下。
  • 绝对需要清理。感谢您修复帖子。
【解决方案4】:

确保正确处理。

using (Image.FromFile("path")) {}

using 表达式是简写

IDisposable obj;
try { }
finally 
{
    obj.Dispose();
}

@Rex 在 Image.Dispose 的情况下,它在 Dispose() 中调用 GdipDisposeImage extern / 本机 Win32 调用。

IDisposable 用作释放非托管资源的机制(文件句柄是)

【讨论】:

  • 在Image的情况下,Dispose到底做了什么?它是否释放文件系统句柄、非托管内存等?
【解决方案5】:

我还尝试了您的所有提示(ReadAllBytes、FileStream=>FromStream=>newBitmap() 来制作副本等),它们都奏效了。但是,我想知道,如果你能找到更短的,并且

using (Image temp = Image.FromFile(path))
{
    return new Bitmap(temp);
}

似乎也可以工作,因为它处理文件句柄以及原始图像对象并创建一个新的位图对象,该对象独立于原始文件,因此可以毫无错误地保存到流或文件中.

【讨论】:

  • 虽然这可能性能较差,但这是唯一对我有用的解决方案。
【解决方案6】:

我不得不将手指指向垃圾收集器。如果您受到垃圾收集的支配,那么将其留在身边并不是真正的问题。

这个人有一个类似的complaint...,他找到了使用 FileStream 对象而不是直接从文件加载的解决方法。

public static Image LoadImageFromFile(string fileName)
{
    Image theImage = null;

    fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    {
        byte[] img;
        img = new byte[fileStream.Length];
        fileStream.Read(img, 0, img.Length);
        fileStream.Close();
        theImage = Image.FromStream(new MemoryStream(img));
        img = null;
    }

...

这似乎是一个完整的黑客......

【讨论】:

  • 也许反对票是因为“看起来像一个完整的黑客”? (不是我)。无论如何,这不是黑客。图像确实使文件保持打开状态,因此如果要断开连接,则必须创建自己的副本。在内存流上打开是一种方法。将图像渲染到另一个图像是另一回事。无论哪种方式,您都不能依赖 Image 与手柄断开连接 - 它会抓住它。 :(
【解决方案7】:

如上所述,Microsoft 解决方法会在加载多个图像后导致 GDI+ 错误。 Steven 上面提到的我的 VB 解决方案是

picTemp.Image = Image.FromStream(New System.IO.MemoryStream(My.Computer.FileSystem.ReadAllBytes(strFl)))

【讨论】:

    【解决方案8】:

    我刚刚遇到了同样的问题,我试图将多个单页 TIFF 文件合并到一个多部分 TIFF 图像中。我需要使用Image.Save() 和'Image.SaveAdd()`:https://msdn.microsoft.com/en-us/library/windows/desktop/ms533839%28v=vs.85%29.aspx

    在我的情况下,解决方案是在处理完每个图像后立即为每个图像调用“.Dispose()”:

    ' Iterate through each single-page source .tiff file
    Dim initialTiff As System.Drawing.Image = Nothing
    For Each filePath As String In srcFilePaths
    
        Using fs As System.IO.FileStream = File.Open(filePath, FileMode.Open, FileAccess.Read)
            If initialTiff Is Nothing Then
                ' ... Save 1st page of multi-part .TIFF
                initialTiff = Image.FromStream(fs)
                encoderParams.Param(0) = New EncoderParameter(Encoder.Compression, EncoderValue.CompressionCCITT4)
                encoderParams.Param(1) = New EncoderParameter(Encoder.SaveFlag, EncoderValue.MultiFrame)
                initialTiff.Save(outputFilePath, encoderInfo, encoderParams)
            Else
                ' ... Save subsequent pages
                Dim newTiff As System.Drawing.Image = Image.FromStream(fs)
                encoderParams = New EncoderParameters(2)
                encoderParams.Param(0) = New EncoderParameter(Encoder.Compression, EncoderValue.CompressionCCITT4)
                encoderParams.Param(1) = New EncoderParameter(Encoder.SaveFlag, EncoderValue.FrameDimensionPage)
                initialTiff.SaveAdd(newTiff, encoderParams)
                newTiff.Dispose()
            End If
        End Using
    
    Next
    
    ' Make sure to close the file
    initialTiff.Dispose()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-11
      • 2011-11-16
      • 1970-01-01
      • 1970-01-01
      • 2010-11-20
      • 1970-01-01
      相关资源
      最近更新 更多