0

I'm using a RenderTargetBitmap to render a set of controls in order to generate a PDF. The following code segment is the relevant section:

public Drawing.Image RenderPageBitmap()
{
    RenderTargetBitmap bit = null;
    Drawing.Bitmap bmp = null;
    try
    {
        bit = new RenderTargetBitmap(ImageSource.PixelWidth, ImageSource.PixelHeight, 96, 96, PixelFormats.Pbgra32);

        var viewBox = GetPageXaml(); //This method loads some prebuilt XAML from an embedded resource, setting the DataContext as needed.
        var siz = new Size(bit.PixelWidth, bit.PixelHeight);
        viewBox.Measure(siz);
        viewBox.Arrange(new Rect(siz));
        viewBox.UpdateLayout();

        var draw = new DrawingVisual();
        using (var graph = draw.RenderOpen())
            graph.DrawRectangle(new BitmapCacheBrush(viewBox), null, new Rect(siz));

        bit.Render(draw);
        bit.Freeze();

        bmp = new Drawing.Bitmap(bit.PixelWidth, bit.PixelHeight, Imaging.PixelFormat.Format32bppPArgb);

        var data = bmp.LockBits(new Drawing.Rectangle(Drawing.Point.Empty, bmp.Size), ImageLockMode.WriteOnly, Imaging.PixelFormat.Format32bppPArgb);
        {
            bit.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);
        }
        bmp.UnlockBits(data);

        return bmp;
    }
    catch (Exception)
    {
        bmp?.Dispose();
        throw;
    }
    finally
    {
        bit?.Clear();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
}

Even following other answers on StackOverflow and other forums (like clearing the bitmap and performing a garbage collection) doesn't seem to solve the issue. Each loop of this code can leak ~100MB of memory, which means I quickly hit the ~2GB limit of 32-bit processes.

The leak seems to occur on the RenderTargetBitmap.Render method exclusively, even the DrawingContext.DrawRectangle call doesn't noticeably increase memory usage.

Is there anything I can do to solve this problem?

Here's a snapshot of the memory usage as viewed through JetBrains' dotMemory. Clearly, the .Net heap is correctly cleared, but the unmanaged memory continues to grow.

Memory snapshot

Olivia Trewin
  • 207
  • 4
  • 6

2 Answers2

0

You return the Bitmap to somewhere. Make sure you Dispose the Bitmap instance once you are done with it. What you are doing in the finally is useless when there is memory leak. If there are references GC wouldn't collect it.

Each loop of this code can leak ~100MB of memory, which means I quickly hit the ~2GB limit of 32-bit processes.

Are you assuming there is a memory leak? May be there is no memory leak. I would get a good memory profiling tool and test this.

I have used ANTS Memory profiler and I find it good (it comes with 14 days trial). Just execute your logic a few times and see the Instance List if anything is growing. If so, look at the Retention graph to see what holds onto it. That will tell you what exactly happening. Root causes for memory leaks are quite difficult to guess sometimes, fortunately there are good tools for that.

CharithJ
  • 46,289
  • 20
  • 116
  • 131
  • The memory leak occurs in unmanaged memory. That garbage collection technique is recommended for `RenderTargetBitmap`s. The `Bitmap` is enclosed in a using block in the caller. – Olivia Trewin May 16 '17 at 01:00
  • @PeterDuniho `System.Drawing.Bitmap` absolutely implements `IDisposable`: https://msdn.microsoft.com/en-us/library/8th8381z(v=vs.110).aspx – Olivia Trewin May 16 '17 at 01:55
  • @la: I was referring to `RenderTargetBitmap`. You didn't show a method declaration, so it was not clear to me that the code you were describing was dealing with a different `Bitmap` class. – Peter Duniho May 16 '17 at 01:57
  • I've used JetBrains' dotMemory and found that the unmanaged memory grows while the managed memory doesn't, which makes it next to impossible to track down with a profiler – Olivia Trewin May 16 '17 at 12:19
0

Remove bit.Freeze();. Garbage collection does not collect frozen objects.

  • "Garbage collection does not collect frozen objects" Sorry, that's incorrect :) – Matt Thomas Jun 02 '22 at 13:40
  • The thing that `bit.Freeze()` _does_ do is forcefully materialize certain lazily-initialized stuff in WPF's render pipeline. That _is_ necessary in certain similar situations, and it probably does increase memory usage, but your explanation isn't right – Matt Thomas Jun 02 '22 at 13:44