Preventing OutOfMemoryException with GC.AddMemoryP

2019-01-23 04:40发布

问题:

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.

回答1:

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.



回答2:

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.