2

In our application we are exporting a lot of images and we ran into this Win32Exception:

System.ComponentModel.Win32Exception (0x80004005): Not enough storage is available to process this command
    at MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook[] hooks)
    at System.Windows.Threading.Dispatcher..ctor()
    at System.Windows.DependencyObject..ctor()
    at System.Windows.Media.Imaging.BitmapSource..ctor(Boolean useVirtuals)
    at System.Windows.Media.Imaging.CachedBitmap..ctor(Int32 pixelWidth, Int32 pixelHeight, Double dpiX, Double dpiY, PixelFormat pixelFormat, BitmapPalette palette, Array pixels, Int32 stride)
    at System.Windows.Media.Imaging.BitmapSource.Create(Int32 pixelWidth, Int32 pixelHeight, Double dpiX, Double dpiY, PixelFormat pixelFormat, BitmapPalette palette, Array pixels, Int32 stride)

The problem is, that we create BitmapSource objects in different threads and there is a memory leak. If you execute this code, there are still a lot of objects in memory:

List<Thread> threads = new List<Thread>();
for (int i = 0; i < 100; i++)
{
    Thread t = new Thread(new ThreadStart(() =>
    {
        BitmapSource temp = BitmapSource.Create(1, 1, 96, 96, PixelFormats.Bgr24, null, new byte[3], 3);
        temp.Freeze();
    }));
    t.Start();
    threads.Add(t);
}
foreach (var thread in threads)
{
    thread.Join();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Screenshot of MemorySnapshot after Execution

We do this in a Windows service and after a few days the system crashes. With the "Not enough storage..."-Message. At this point a lot of other applications crash, you can't open up an editor or other Windows applications. Could it be, that the Desktop Heap is full? The memory doesn't go up much (160MB max) and the machine has 16GB of RAM.

How do we dispose the BitmapSource correctly?

tomfroehle
  • 602
  • 5
  • 16
  • Do you keep a reference somewhere to the `BitmapSource` object? The GC should clean up once the BitmapSource is no longer used, according to http://stackoverflow.com/a/1592010/40347 – Dirk Vollmar Dec 04 '15 at 10:24
  • Nope, this is the whole code snippet that leads to a memory and possible handle leak – tomfroehle Dec 04 '15 at 10:36
  • I see the problem now. It only occurs if the BitmapSource is created on its own thread. Might be a bug/unsupported scenario (looks like the Dispatcher might keep a reference that prevents garbage collection) – Dirk Vollmar Dec 04 '15 at 10:38

3 Answers3

0

We found a solution, where we don't see a memroy leak:

List<Thread> threads = new List<Thread>();
for (int i = 0; i < 100; i++)
{
    Thread t = new Thread(new ThreadStart(() =>
    {
        BitmapSource temp = BitmapSource.Create(1, 1, 96, 96, PixelFormats.Bgr24, null, new byte[3], 3);

        // Shutdown Dispatcher
        if (temp.Dispatcher != null)
        {
            temp.Dispatcher.InvokeShutdown();
        }

        temp.Freeze();
    }));
    t.Start();
    threads.Add(t);
}
foreach (var thread in threads)
{
    thread.Join();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

We have to see if this causes other problems, what do you think?

tomfroehle
  • 602
  • 5
  • 16
  • Which dispatcher are you shutting down when you call `temp.Dispatcher.InvokeShutdown();`, are new dispatchers being created? – Scott Chamberlain Dec 04 '15 at 15:17
  • From my understanding its the dispatcher that was created to create the bitmapsource but its also the dispatcher of the thread – tomfroehle Dec 05 '15 at 13:14
0

The memory leak seems related to the bitmaps not being created on the main thread and the dispatcher keeping a reference. The solution you found might work.

However, I'd like to suggest another improvement. Using an unlimited number of threads might not be a good idea. It might make sense to use a thread pool. The easiest way would be to use Task.Run to schedule the bitmap creation on the thread pool. In fact, I didn't see any memory leaks when using the thread pool.

var tasks = new List<Task>();
for (int i = 0; i < 100000; i++)
{
    var task = Task.Run(() =>
    {
        BitmapSource temp = null;
        temp = BitmapSource.Create(
            1,
            1,
            96,
            96,
            PixelFormats.Bgr24,
            null,
            new byte[3],
            3);
        temp.Freeze();
    });
    tasks.Add(task);
}

Task.WaitAll(tasks.ToArray());
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • Thanks for the input. we also found that using a threadpool we didnt see a memory leak. unfortunately we don't control how the method that creates the BitmapSource is created. The application uses DirectShow and i think that they use new threads for every callback (that triggers the method) – tomfroehle Dec 05 '15 at 13:23
-1

As you can see in .NET Reference Source, BitmapSource.Create simply calls CachedBitmap constructor which, by default, uses BitmapCachinOptions.Default as caching options. It means that every bitmap created will be held in cache.

You need to used some other class inherited from BitmapSoure that doesn't use caching. You could try WritableBitmap if you want to create bitmap yourself.

ghord
  • 13,260
  • 6
  • 44
  • 69