16

I'm currently debugging a method we use to tag images with a certain text before displaying them in our system.

The tag method looks like this at the moment:

private static Image TagAsProductImage(Image image)
{
    try
    {
        // Prepares the garbage collector for added memory pressure (500000 bytes is roughly 485 kilobytes).
        // Should solve some OutOfMemoryExceptions.
        GC.AddMemoryPressure(500000);

        using (Graphics graphics = Graphics.FromImage(image))
        {
            // Create font.
            Font drawFont = new Font("Tahoma", image.Width*IMAGE_TAG_SIZE_FACTOR);

            // Create brush.
            SolidBrush drawBrush = new SolidBrush(Color.Black);

            // Create rectangle for drawing.
            RectangleF drawRect = new RectangleF(0, image.Height - drawFont.GetHeight(), image.Width,
                                                    drawFont.GetHeight());

            // Set format of string to be right-aligned.
            StringFormat drawFormat = new StringFormat();
            drawFormat.Alignment = StringAlignment.Far;

            // Draw string to screen.
            graphics.DrawString(TAG_TEXT, drawFont, drawBrush, drawRect, drawFormat);
        }
    }
    // If an out of memory exception is thrown, return the unaltered image.
    catch(OutOfMemoryException)
    {
        GC.RemoveMemoryPressure(500000);
        return image;
    }

    GC.RemoveMemoryPressure(500000);
    return image;
}

To put things in context: This method is being called after an image has been retrieved from our image server and saved to a local cache (that our system shares with other systems that needs the same pictures).

We've been having problems with OutOfMemoryExceptions when reaching using (Graphics... (when the image needs to be retrieved from the server prior to tagging, if the image exists in the cache the tagging hasn't been a problem).

To prevent/circumvent the OutOfMemoryException I've tried three different approaches, and while they work I don't really like any of them.

First I tried doing a generic GC.Collect(); before calling Graphics.FromImage(image) which worked (of course) but I don't like forcing Collects since it leaves a big hit on performance.

My second approach was to call GC.Collect() in the catch-statement and then recursively calling TagAsProductImage(image) but this is might lead to a infinite loop if GC fails to free up enough memory.

And finally I ended up with the above code, which I can't say I'm to fond of either.

I can probably get away with using GC.Collect() since the whole operation of getting the image from the service -> saving -> tagging is quite a big one so the performance hit from the collect will be minimal but I'd really like a better solution.

If anyone have a smart solution to this, please share.

Sam
  • 7,252
  • 16
  • 46
  • 65
Mantisen
  • 365
  • 3
  • 12

2 Answers2

17

If you are looking for a way to ensure you'll have enough memory available for an operation, use MemoryFailPoint.

With this, through a using, you can define a region in which you will need a certain amount of memory. If that isn't available, it will throw a recoverable InsufficientMemoryException.

See http://msdn.microsoft.com/en-us/library/system.runtime.memoryfailpoint.aspx for more information.

Pieter van Ginkel
  • 29,160
  • 8
  • 71
  • 111
  • I did not know about the MemoryFailPoint, so thank you for the heads up it's going straight to my "Useful code to remember" corner. Unfortunately it didn't help, I tried allocating everything from 1 to 200 MB, it succeeded in all cases but still threw an OutOfMemoryException when trying to create the Graphics object. Will have to keep looking (GDI+ and memory handling can be a pain sometimes). – Mantisen Nov 11 '10 at 14:17
  • I take it it fails it doesn't always fail??? What you should do is before the using, do a `GC.GetTotalMemory(false)` and just before the end of the using block, another one. The difference should be the actual amount of memory that is used for that process. If providing that amount to `MemoryFailPoint` doesn't work then, well, I'm out of ideas :P. – Pieter van Ginkel Nov 11 '10 at 14:24
  • You are absolutely right, it only fails when the image wasn't previously in the cache but was retrieved from our image server right before this method is called. The weird thing is that `MemoryFailPoint` manages to allocate memory (every time!) but `using (Graphics...` throws an OutOfMemoryException anyway if the image wasn't previously in the cache-folder. – Mantisen Nov 11 '10 at 14:32
  • How big is the image? bpp and dimensions? And I can't really place the from cache or not. How is the `Image` created if it doesn't come from cache (the failing case)? – Pieter van Ginkel Nov 11 '10 at 14:42
  • The images are often smaller than 200 kB. If they're not stored locally (i.e. in the cahce-folder of the appliction) they are retrieved from a service and then saved as PNGs, but they might reach the tag method as either jpg's or png's (if they are downloaded as jpg's they are not stored and then retrieved from the local folder). Even though this specific answer didn't solve my problem I will mark this as an answer since I was really looking for alternatives to what I'm doing right now and your solution seems to be a good alternative. – Mantisen Nov 11 '10 at 14:56
  • I think it's clear by now that you have a different problem going on. 200K is nothing and I believe this has nothing to do with memory. Can you reproduce this error in a console project? – Pieter van Ginkel Nov 11 '10 at 15:06
  • Agreed, check my post for another scenario. – Hans Passant Nov 11 '10 at 15:28
  • D'oh. Of course, I see it now. The objects aren't disposed. @Mantisen - Check all objects you are creating and dispose them either in a using or a try/finally, as @Hans Passant explained. – Pieter van Ginkel Nov 11 '10 at 15:50
10

You are having a different problem here, this code uses very little memory. Sadly GDI+ exceptions are pretty crummy. Diagnose this with TaskMgr.exe, Processes tab. View + Select Columns and tick GDI Objects, Handles and USER Objects.

If my suspicion is correct, you'll see the GDI Objects counter for your process climbing constantly as this code runs. When it reaches 10,000 Windows decides that there's something fundamentally wrong with the code and refuses to create any more handles. GDI+ then gets a bit gimpy about it and reports an out of memory error. Wrong, it should have been a 'could not create handle' error. An error code that it doesn't have. .NET is powerless to improve the exception.

Anyhoo, the reason is that you are not calling Dispose() on the font and brush. Wrap them with the using statement. This normally doesn't cause trouble but your program is apparently using too little garbage collected memory to ever kick off the finalizer thread.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • The code I've included is just a small method in an otherwise quite large class (see the description of in which context the method is being called) and that class is in turn a part of a very data heavy system with many users. I've managed to trace this specific error to an OutOfMemoryException in this particular method (using perfmon, taskmanager, etc.), on top of that the exception is thrown before using the brush or font and since I have them inside a using for the Graphics they should be disposed when the Graphics object is. Shouldn't they? But I do agree that GDI+ exceptions are crummy. – Mantisen Nov 11 '10 at 13:58
  • 2
    No, Graphics doesn't automatically dispose anything. – Hans Passant Nov 11 '10 at 14:15
  • Not even when put in a `using` statement? I've always thought that anything put in a using statement was disposed when exiting the using scope. – Mantisen Nov 11 '10 at 14:29
  • 1
    That only disposes the Graphics object, not any drawing object you use to actually draw. The Graphics object cannot keep track of what you created, note that you didn't pass a reference to it when you created the Font for example. – Hans Passant Nov 11 '10 at 14:33
  • Thanks for clearing that up, I'm adding `.Dispose()` everywhere I can in the class now. That might clear up enough space for this method. – Mantisen Nov 11 '10 at 14:41
  • Focus on the Task Manager diagnostic that I documented. Dispose() stops that number from growing. It doesn't actually 'clear up enough space', Dispose() doesn't release any managed memory. And watch out for those bitmaps, they are very often the real problem because they eat so much unmanaged memory to store their pixels. Calling their Dispose() method is *very* critical to release that memory. – Hans Passant Nov 11 '10 at 15:26
  • +1 The reason they aren't disposed is probably because the server has enough memory. Why should the GC dispose them?!? – Pieter van Ginkel Nov 11 '10 at 15:51
  • @Pieter - the finalizer thread does. This can only really go wrong if he's not allocating enough GC memory. Which is possible, the managed font, brush, pen etc wrapper classes are quite small. 10,000 of them isn't enough to kick off a GC but enough to make Windows grumpy. – Hans Passant Nov 11 '10 at 15:56
  • @Hans Passant - That was my theory. – Pieter van Ginkel Nov 11 '10 at 16:45