Caching Color-Bitmaps as MemoryStreams I am in the need to cache Bitmap's in a memory-optimized way because the API I am building will need to process many colored Bitmap's in parallel and can be used in x86 or x64 compiled applications. 

If the API is being used in x86 I can't just store the Bitmap's as they are but need to store them as compressed MemoryStream's otherwise the API would throw an OutOfMemoryException pretty fast.  

After storing the "Bitmap's" they will be processed by multiple threads hence thread-safety is a major point.  

Any feedback is welcome.  

public static class ImageCache
{
    private static int currentId = 0;
    private static readonly object addImageLock = new object();
    private static readonly object releaseImageLock = new object();
    private static readonly ConcurrentDictionary&lt;int, MemoryStream&gt; images = new ConcurrentDictionary&lt;int, MemoryStream&gt;();

    /// &lt;summary&gt;
    /// Release an image based on its id.
    /// &lt;/summary&gt;
    /// &lt;param name="id"&gt;&lt;/param&gt;
    public static void ReleaseIamge(int id)
    {
        lock (releaseImageLock)
        {
            ReleaseMemoryStream(id);
        }
    }

    private static void ReleaseMemoryStream(int id)
    {
        MemoryStream ms = null;
        if (images.TryGetValue(id, out ms) &amp;&amp; ms != null)
        {
            images[id].Dispose();
            images[id] = null;
        }
    }

    /// &lt;summary&gt;
    /// Releases all Images
    /// &lt;/summary&gt;
    public static void ReleaseAllImages()
    {
        lock (releaseImageLock)
        {
            lock (addImageLock)
            {
                foreach (var id in images.Keys)
                {
                    ReleaseMemoryStream(id);
                }
                images.Clear();
            }
        }
    }

    /// &lt;summary&gt;
    /// Returns a Bitmap from the cache which is identified by an id
    /// &lt;/summary&gt;
    /// &lt;param name="id"&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public static Bitmap GetBitmap(int id)
    {
        lock (releaseImageLock)
        {
            MemoryStream ms = null;
            if (images.TryGetValue(id, out ms))
            {
                if (ms != null)
                {
                    return (Bitmap)Image.FromStream(ms);
                }
            }
        }
        return null;
    }

    /// &lt;summary&gt;
    /// Adds an Bitmap to the cache
    /// &lt;/summary&gt;
    /// &lt;param name="bitmap"&gt;&lt;/param&gt;
    /// &lt;returns&gt;0 if the Bitmap is null, otherwise a uique id&lt;/returns&gt;
    public static int Add(Bitmap bitmap)
    {

        if (bitmap == null)
        {
            return 0;
        }

        var ms = new MemoryStream();
        bitmap.Save(ms, ImageFormat.Tiff);
        var id = 0;
        lock (addImageLock)
        {
            // If the dictionary is empty we can reset the currentId
            if (images.Count == 0)
            {
                currentId = 0;
                id = Interlocked.Increment(ref currentId);
                images.TryAdd(id, ms);
                return id;
            }

            // We don't know how long an application using this is running and how many
            // images having been stored but we don't want to reach int.MaxValue here
            // hence we recycle the Value of a KeyValuePair of the dictionary if the Value
            // will be null.
            id = images.Where(item =&gt; item.Value == null).FirstOrDefault().Key;
            if (id == 0)
            {
                id = Interlocked.Increment(ref currentId);
            }

            images[id] = ms;

        }
        return id;
    }
}


Clarifying comments:


  Why don't you use a Guid as id and key in the dictionary?  


Well the problem with this is that I need to "mimic" an API we use for our main application in such a way that I can interchange this API and the other API. The other api is a comercial imaging sdk whichs licence doesn't permits the use of it for a sdk/api.
First I'm not a fan of recycling IDs. Too much confusion can happen.  For example Thread 1 adds a Dog Image and gets ID 1.   Thread 2 calls ReleaseAllImages and then adds a Cat image and gets ID 1.  Now Thread 1 think it has a Dog image but instead returns back a Cat image.  There are times we need to recycle ID but only you know the business requirements if it's truly needed or not. 

Second It's thread safe but you are not getting the benefit of ConcurrentDictionary this way. Everything is locked.  If two threads are trying to add one will be blocked until the other is free.  It doesn't have to be this way.  

I'm going to go down the path that you need to recycle IDs

Instead of leaving the record in the dictionary you can remove it and have a queue of ID to recycle.  Then on the removing methods check if we removed all the records from the Dictionary and if so then enter the lock to see if we can reset the counter and queue. Going to use ReaderWriterLockSlim to allow multiple adds and only block when we are removing.

public static class ImageCache
{
    private static int currentId = 0;

    private static readonly ConcurrentDictionary&lt;int, MemoryStream&gt; images =
        new ConcurrentDictionary&lt;int, MemoryStream&gt;();

    // stores removed ID to reuse
    private static ConcurrentQueue&lt;int&gt; recycle = new ConcurrentQueue&lt;int&gt;();
    private static readonly ReaderWriterLockSlim recycleLocker = new ReaderWriterLockSlim();

    /// &lt;summary&gt;
    /// Release an image based on its id.
    /// &lt;/summary&gt;
    /// &lt;param name="id"&gt;&lt;/param&gt;
    public static void ReleaseIamge(int id)
    {
        ReleaseMemoryStream(id);
        TryReset();
    }

    /// &lt;summary&gt;
    /// Releases all Images
    /// &lt;/summary&gt;
    public static void ReleaseAllImages()
    {
        foreach (var id in images.Keys)
        {
            ReleaseMemoryStream(id);
        }

        TryReset();
    }

    private static void ReleaseMemoryStream(int id)
    {
        MemoryStream ms = null;
        if (images.TryRemove(id, out ms))
        {
            recycle.Enqueue(id);
            ms.Dispose();
        }
    }

    private static void TryReset()
    {
        if (!images.IsEmpty)
        {
            return;
        }

        //need to lock here
        if (recycleLocker.TryEnterWriteLock(TimeSpan.FromMilliseconds(100)))
        {
            try
            {
                // make sure another thread didn't sneak in and add another image
                if (images.IsEmpty)
                {
                    currentId = 0;
                    Interlocked.Exchange(ref recycle, new ConcurrentQueue&lt;int&gt;());
                }
            }
            finally
            {
                recycleLocker.ExitWriteLock();
            }
        }
    }


GetBitmap method doesn't need to check if the image is null now we can use the TryGetValue and not need to lock at all 

/// &lt;summary&gt;
/// Returns a Bitmap from the cache which is identified by an id
/// &lt;/summary&gt;
/// &lt;param name="id"&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static Bitmap GetBitmap(int id)
{
    MemoryStream ms = null;
    if (images.TryGetValue(id, out ms))
    {
        return (Bitmap) Image.FromStream(ms);
    }
    return null;
}


Add method will need to let the lock know we are reading from the recycle queue 

/// &lt;summary&gt;
/// Adds an Bitmap to the cache
/// &lt;/summary&gt;
/// &lt;param name="bitmap"&gt;&lt;/param&gt;
/// &lt;returns&gt;0 if the Bitmap is null, otherwise a uique id&lt;/returns&gt;
public static int Add(Bitmap bitmap)
{
    if (bitmap == null)
    {
        return 0;
    }

    recycleLocker.EnterReadLock();

    try
    {
        var ms = new MemoryStream();
        bitmap.Save(ms, ImageFormat.Tiff);

        // Recycle Id or make new one
        int id;
        if (!recycle.TryDequeue(out id))
        {
            id = Interlocked.Increment(ref currentId);
        }

        // this should never be possible to fail
        images.TryAdd(id, ms);

        return id;
    }
    finally 
    { 
        recycleLocker.ExitReadLock();
    }
}


Using the ReaderLockerSlim will allow multiple reads but only one write.  Now multiple threads could Add images and Get images without blocking each other and the code only blocks when it's resetting the counters.