12

I'm wondering how does the allocation and disposal of memory allocated for bitmaps work in .NET.

When I do a lot of bitmap creations in loops in a function and call it in succession it will work up until at some point the Bitmap wont be able to allocate memory giving the exception "Invalid parameter" for the size specified.

If I call the garbage collector from while to while it works.

With the following code you are able to repoduce the error:

class BitmapObject {
    public bool Visible {
        get { return enb; }
        set { enb = value; }
    }
    private bool enb;
    private Bitmap bmp;
public BitmapObject(int i, bool en)
{
    enb = en;
    bmp = new Bitmap(i, i);


   }
}

class Pool<T> where T : BitmapObject
{
    List<T> preallocatedBitmaps = new List<T>();
public void Fill() {
    Random r = new Random();
    for (int i = 0; i < 500; i++) {
        BitmapObject item = new BitmapObject(500, r.NextDouble() > 0.5);
        preallocatedBitmaps.Add(item as T);
    }
}

public IEnumerable<T> Objects
{
    get
    {
        foreach (T component in this.preallocatedBitmaps)
        {
            if (component.Visible)
            {
                yield return (T)component;
            }
        }


     }
    }
}

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
{
    for (int i = 0; i < 10; i++ )
    {
        Test();

            // without this it breaks
            //GC.Collect();
            //GC.WaitForPendingFinalizers();
        }

        Console.ReadKey();
    }

    private static void Test() {
        Pool<BitmapObject> pool = new Pool<BitmapObject>();
        pool.Fill();

        for (int i = 0; i < 100; i++)
        {
            var visBitmaps = pool.Objects;
            // do something
        }       
     }
}
Marino Šimić
  • 7,318
  • 1
  • 31
  • 61
  • 2
    The GC *never* cleans up native resources, only the Bitmap object itself. You are responsible for calling `Dispose()`. – Ed S. Apr 30 '11 at 00:28
  • @Ed, while technically true, when the GC cleans up the Bitmap the Bitmap's finalizer will dispose of the resource. But you are correct that you shouldn't rely on that. – Talljoe Apr 30 '11 at 00:33
  • @Talljoe: Yes, you're right, it will. – Ed S. Apr 30 '11 at 00:42

3 Answers3

19

The Bitmap class is inevitably the one where you have to stop ignoring that IDisposable exists. It is a small wrapper class around a GDI+ object. GDI+ is unmanaged code. The bitmap occupies unmanaged memory. A lot of it when the bitmap is large.

The .NET garbage collector ensures that unmanaged system resources are released with the finalizer thread. Problem is, it only kicks into action when you create sufficient amounts of managed objects to trigger a garbage collection. That won't work well for the Bitmap class, you can create many thousands of them before generation #0 of the garbage collected heap fills up. You will run out of unmanaged memory before you can get there.

Managing the lifetime of the bitmaps you use is required. Call the Dispose() method when you no longer have a use for it. That's not always the golden solution, you may have to re-think your approach if you simply have too many live bitmaps. A 64-bit operating system is the next solution.

Pang
  • 9,564
  • 146
  • 81
  • 122
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
17

The .NET Bitmap class "encapsulates a GDI+ bitmap", that means you should call Dispose on a Bitmap when you are finished with it,

"Always call Dispose before you release your last reference to the Image. Otherwise, the resources it is using will not be freed until the garbage collector calls the Image object's Finalize method."

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • That means if some bitmaps are used in generic classes then generic classes should implement IDIsposable and the T must be disposable to be called by the class? – Marino Šimić Apr 30 '11 at 00:33
1

Why don't you use using keyword. Just encapsulate your Bitmap object in it and Compiler will ensure that Dispose method is called.

Its simply a syntactic shortcut for

try
{
 ...   
}
finally
{
    ...Dispose();
}
Shekhar_Pro
  • 18,056
  • 9
  • 55
  • 79
  • 1
    because the bitmap can be a private field of a class that is used in a generic outer list... but i think IDisposable is the way to go here... – Marino Šimić Apr 30 '11 at 00:36