Why does Image.FromFile keep a file handle open sometimes?
Asked Answered
A

8

23

I am doing a lot of image processing in GDI+ in .NET in an ASP.NET application.

I frequently find that Image.FromFile() is keeping a file handle open.

Why is this? What is the best way to open an image without the file handle being retained.

  • NB: I'm not doing anything stupid like keeping the Image object lying around - and even if I was I woudlnt expect the file handle to be kept active
Anandrous answered 25/4, 2009 at 6:18 Comment(1)
Are you sure that FromFile is doing that? Silly, I know but you can use handle (SysInternal utility) to verify that the handle indeed comes from FromFile.Sellma
S
37

I went through the same journey as a few other posters on this thread. Things I noted:

  1. Using Image.FromFile does seem unpredictable on when it releases the file handle. Calling the Image.Dispose() did not release the file handle in all cases.

  2. Using a FileStream and the Image.FromStream method works, and releases the handle on the file if you call Dispose() on the FileStream or wrap the whole thing in a Using {} statement as recommended by Kris. However if you then attempt to save the Image object to a stream, the Image.Save method throws an exception "A generic error occured in GDI+". Presumably something in the Save method wants to know about the originating file.

  3. Steven's approach worked for me. I was able to delete the originating file with the Image object in memory. I was also able to save the Image to both a stream and a file (I needed to do both of these things). I was also able to save to a file with the same name as the originating file, something that is documented as not possible if you use the Image.FromFile method (I find this weird since surely this is the most likely use case, but hey.)

So to summarise, open your Image like this:

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

You are then free to manipulate it (and the originating file) as you see fit.

Sublunary answered 9/7, 2009 at 17:23 Comment(5)
The webapp I have in production that uses this technique appears to leak memory. I have it running on 6 webservers and after a few days the relevant ASP.NET process is using > 2gb of memory. I have hacked in a fix by setting the relevant application pool to recycle after it hits 500mb of memory usage or every day at 2am. I don't have time to investigate this at present, but when I do I will post here with my solution.Sublunary
Did you find/fix your problem?Eastman
@Eastman - sadly not! I seem to remember reading in the MSDN that use of the GDI+ classes is not supported in services / web applications and I can see why. The memory usage is incredibly high. Ironically, part of my problem is that I've done things 'properly' with a data model that encapsulates all of the logic - this makes it a little more difficult to tear this stuff down after it's been used. Our solution is to use a seperate application server for this functionality - the system was SOA anyway so it's not too much pain for us.Sublunary
With regards to your number 2 point, it could be because you're using a non-seekable stream which is why the memory stream works. For saving you can try (pseudocode): a using(filestream) { using(new memorystream) { i.Save(memstream); memstream.WriteTo(fs); } }Curator
do the three created objects (File, MemoryStream and Image) need to be disposed?Fricassee
C
16

I have had the same problem and resorted to reading the file using

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

Cogitate answered 25/4, 2009 at 6:45 Comment(1)
Not sure why my answer was voted down. It will read without keeping the handle open.Cogitate
L
6

Image.FromFile keeps the file handle open until the image is disposed. From the MSDN:

"The file remains locked until the Image is disposed."

Use Image.FromStream, and you won't have the problem.

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

Edit: (a year and a bit later)

The above code is dangerous as it is unpredictable, at some point in time (after closing the filestream) you may get the dreaded "A generic error occurred in GDI+". I would amend it to:

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;
Leek answered 25/4, 2009 at 16:20 Comment(2)
I think the FileStream also needs to be cleaned up.Vargas
Absolutely needs to be cleaned up. Thanks for fixing up the post.Leek
A
2

I also tried all your tips (ReadAllBytes, FileStream=>FromStream=>newBitmap() to make a copy, etc.) and they all worked. However, I wondered, if you could find something shorter, and

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

appears to work, too, as it disposes the file handle as well as the original Image-object and creates a new Bitmap-object, that is independent from the original file and therefore can be saved to a stream or file without errors.

Aquiver answered 25/4, 2011 at 11:20 Comment(1)
Although this is probably less performant, this is the only solution that seems to work for me.Haroun
C
1

Make sure you are Disposing properly.

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

The using expression is shorthand for

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

@Rex in the case of Image.Dispose it calls GdipDisposeImage extern / native Win32 call in it's Dispose().

IDisposable is used as a mechanism to free unmanaged resources (Which file handles are)

Contiguous answered 25/4, 2009 at 6:53 Comment(1)
In the case of Image, what all does Dispose actually do? Does it release file system handles, unmanaged memory, etc.?Vargas
W
0

I would have to point my finger at the Garbage Collector. Leaving it around is not really the issue if you are at the mercy of Garbage Collection.

This guy had a similar complaint... and he found a workaround of using a FileStream object rather than loading directly from the file.

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;
    }

...

It seems like a complete hack...

Witted answered 25/4, 2009 at 6:32 Comment(1)
maybe the downvote was for "seems like a complete hack"? (not I). Anyway, it's not a hack. Image does keep the file open, so if you want to disconnect, you have to create your own copy. Opening on a memory stream is one way. Rendering the image to another Image is another. Either way, you cannot rely on Image disconnecting from the handle - it holds on to it. :(Ens
S
0

As mentioned above the Microsoft work around causes a GDI+ error after several images have been loaded. The VB solution for me as mentioned above by Steven is

picTemp.Image = Image.FromStream(New System.IO.MemoryStream(My.Computer.FileSystem.ReadAllBytes(strFl)))
Samples answered 12/7, 2009 at 20:13 Comment(0)
T
0

I just encountered the same problem, where I was trying to merge multiple, single-page TIFF files into one multipart TIFF image. I needed to use Image.Save() and 'Image.SaveAdd()`: https://msdn.microsoft.com/en-us/library/windows/desktop/ms533839%28v=vs.85%29.aspx

The solution in my case was to call ".Dispose()" for each of the images, as soon as I was done with them:

' 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()
Tenth answered 6/12, 2015 at 2:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.