Navigation:  I Want To... > Miscellaneous >

Add my object to memory management

Previous pageReturn to chapter overviewNext page

Use Case

Your code creates and references large objects (larger than 85 000 bytes in size), and you want the Workstation's built-in memory manager to be aware of these objects to avoid a potential System.OutOfMemoryException.

Relevant Architecture

None.

Relevant Types

ILargeObjectContainer
LargeObjectContainerData
MemoryManager

Sample Code

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using ClearCanvas.ImageViewer.Common;
 
namespace MyPlugin.Miscellaneous
{
   public class MemoryManagedBitmap : IDisposable, ILargeObjectContainer
   {
      private readonly LargeObjectContainerData _largeObjectData = new LargeObjectContainerData(Guid.NewGuid());
      private readonly object _syncLock = new object();
      private readonly string _filename;
      private volatile byte[] _pixelData;
 
      public MemoryManagedBitmap(string filename)
      {
         _filename = filename;
      }
 
      public void Dispose()
      {
         // force an unload if we're being disposed
         this.Unload();
      }
 
      public byte[] PixelData
      {
         get
         {
            // update the last access time
            _largeObjectData.UpdateLastAccessTime();
 
            // if the data is already available without blocking, return it immediately
            byte[] pixelData = _pixelData;
            if (pixelData != null)
               return pixelData;
 
            // wait for synchronized access
            lock (_syncLock)
            {
               // if the data is now available, return it immediately
               // (i.e. we were blocked because we were already reading the data)
               if (_pixelData != null)
                  return _pixelData;
 
               // read the bitmap into memory
               using (Bitmap bmp = new Bitmap(_filename))
               {
                  BitmapData bmpData = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                  try
                  {
                     _pixelData = new byte[bmpData.Stride*bmpData.Height];
                     Marshal.Copy(bmpData.Scan0, _pixelData, 0, _pixelData.Length);
                  }
                  finally
                  {
                     bmp.UnlockBits(bmpData);
                  }
               }
 
               // update our stats
               _largeObjectData.BytesHeldCount = _pixelData.Length;

               _largeObjectData.LargeObjectCount = 1;
 
               // regenerating the pixel data is easy when it's stored on the hard drive!
               _largeObjectData.RegenerationCost = RegenerationCost.Low;
 
               // register with memory manager
               MemoryManager.Add(this);
 
               return _pixelData;
            }
         }
      }
 
      public void Unload()
      {
         // wait for synchronized access
         lock (_syncLock)
         {
            // dump our data
            _pixelData = null;
 
            // update our stats
            _largeObjectData.BytesHeldCount = 0;

            _largeObjectData.LargeObjectCount = 0;
 
            // unregister with memory manager
            MemoryManager.Remove(this);
         }
      }
 
      // ... (Other ILargeObjectContainer Members here)
   }
}

Remarks

If you have code that routinely creates and references large objects1 over 85 000 bytes in size, you should consider adding your objects to the built-in memory manager.  In the event that the end-user starts running into low memory situations while running the Workstation, the memory manager will try to preemptively unload some low-priority objects in order to bring the process memory usage back down to a manageable level, thereby preventing the dreaded System.OutOfMemoryException.  In order for the memory manager to do so, it must first be aware of the objects that it can unload, and various statistics about each object such as how often it is accessed, how big it is, and how costly it would be to subsequently reload the data if it were to be unloaded.  When the memory manager detects a low memory condition, it will rank all its tracked objects by these statistics, and unload them one by one until it resolves the low memory condition.

To make the memory manager aware of your object, your class should implement the ClearCanvas.ImageViewer.Common.ILargeObjectContainer interface.  Most of the interface members are for the purposes of identifying your object and providing the various statistics – for your convenience, you can keep an instance of a LargeObjectContainerData which has settable fields and other useful update methods.  The most important member is the Unload() method which, as you may have guessed, is called by the memory manager to request that your object be unloaded from memory.  Since your code must therefore be prepared to reload its data each time it is accessed, a common pattern to use here is the "lazy load" in which the accessor checks the field for null and loads the data on demand if necessary.

N.B.: As the call to unload the data will come from a different thread, you should be fairly comfortable with multithreading and know what it means to be thread safe, before attempting to implement memory management on your object.  There are a number of good resources on the Internet that are just a few clicks away from your favourite search engine.

Examples in Code Base

ClearCanvas.ImageViewer.Imaging.ComposedLutCache.CacheItem
ClearCanvas.ImageViewer.StudyManagement.StandardSopDataSource.StandardSopFrameData

1  "Large object" is a .NET technical term referring to objects over 85 000 bytes in size, which are allocated on the large object heap.  This threshold may not seem like a lot, but it was determined through optimization testing to be the best for performance in the managed .NET environment.