wpf: Known issue: WPF will throw COM Exception when create RenderTargetBitmap too fast

  • .NET Core Version: 3.1.300
  • Windows version: 10.0.18362
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes

Problem description:

When we use RenderTargetBitmap to take the control screenshots, if our do it too frequent and too fast, then we will find that the framework throws an exception.

Actual behavior:

 System.Runtime.InteropServices.COMException: MILERR_WIN32ERROR

System.Runtime.InteropServices.COMException (0x88980003): MILERR_WIN32ERROR (Exception from HRESULT:0x88980003)
   in System.Windows.Media.Imaging.RenderTargetBitmap.FinalizeCreation()
   in System.Windows.Media.Imaging.RenderTargetBitmap..ctor(Int32 pixelWidth, Int32 pixelHeight, Double dpiX, Double dpiY, PixelFormat pixelFormat)

Expected behavior:

No exception

Minimal repro:

https://github.com/dotnet-campus/wpf-issues/tree/master/RenderTargetBitmapThrowsCOMExceptionWhenCreatedTooFast

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Comments: 17 (6 by maintainers)

Most upvoted comments

The workaround without forcing the GC is to manually release the handle in the RenderTargetBitmap using reflection:

(bitmap.GetType().GetField("_renderTargetBitmap", BindingFlags.Instance | BindingFlags.NonPublic)?
.GetValue(bitmap) as IDisposable)?.Dispose();

Yeah, from my experience if you are running a x64 process (not the default setting anymore) and have plenty of RAM, the finalizers do eventually start kicking in.

As for fix, the BitmapSource should be IDisposable and release the handle/IWICBitmap on disposal rather than in a finalizer.

@JensNordenbro We are trying to find a solution, thank you.

The only way I have found to reliably force the cleanup of the GDI handles and ensure memory use drops immediately is with the following hack:

// WPF Hack: Imaging - Force the RenderTargetBitmap to release memory handles immediately
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
var fieldWicSourceHandle = typeof(RenderTargetBitmap).GetField("_wicSource", flags);
var fieldRenderTargetBitmap = typeof(RenderTargetBitmap).GetField("_renderTargetBitmap", flags);
var methodReleaseHandle = fieldRenderTargetBitmap.FieldType.GetMethod("ReleaseHandle", flags);
methodReleaseHandle.Invoke(fieldWicSourceHandle.GetValue(rtb), null);
methodReleaseHandle.Invoke(fieldRenderTargetBitmap.GetValue(rtb), null);
fieldWicSourceHandle.SetValue(rtb, null);
fieldRenderTargetBitmap.SetValue(rtb, null);
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

As @miloush and @lindexi note, BitmapSource should use IDisposable to release these resources and save developers this headache.

The GDI count increments by two every time this function here is invoked: image

This is being invoked in a background dispatcher thread (a different thread than the main UI, but the thread is running a Dispatcher).

Here’s my code: image

Bump. I’m running into this issue when I use lots of RenderTargetBitmap instances in a parallel rendering scenario. GDI object count is hitting the upper limit of 10,000.

Any news? help…

I modified your sample code by counting the GDI handles at the point when the exception is thrown:

[System.Runtime.InteropServices.DllImport("User32")]
private extern static int GetGuiResources(IntPtr hProcess, int uiFlags);

private static int GetGDIHandleCount()
{
     using (var process = System.Diagnostics.Process.GetCurrentProcess())
     {
          var gdiHandles = GetGuiResources(process.Handle, 0);
          return Convert.ToInt32(gdiHandles);
     }
}
// RenderTargetBitmap throws COM exception when created too fast: MILERR_WIN32ERROR (Exception from HRESULT: 0x88980003)
// The count is always equal to the per-process GDI handle limit in the following registry key:
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota
Console.WriteLine("GDI handles {0}: ", GetGDIHandleCount());
Console.WriteLine(e);

It always yields the following output when the exception is thrown:

GDI handles 10000: 
System.Runtime.InteropServices.COMException (0x88980003): MILERR_WIN32ERROR (Exception from HRESULT: 0x88980003)
   at System.Windows.Media.Imaging.RenderTargetBitmap.FinalizeCreation()

In other words, FinalizeCreation() only throws this exception when the GDI handle count reaches the per-process limit. The cause is that release of these GDI handles is not immediate, so there is no guarantee that there will be enough GDI handles left when a new RenderTargetBitmap object is created. Subsequent use of RenderTargetBitmap only succeeds once the GDI handle count has dropped below the per-process limit again.

The call to GC.WaitForPendingFinalizers(); in the original sample just reduces the probability of reaching the per-process limit before handles have been released. Increasing the delay in the task creation also helps. It is certainly hardware dependent. In fact, I had to remove the call to GC.WaitForPendingFinalizers(); and decrease the delay to get the exception to throw without running the sample for quite some time.

My understanding of this issue is that one or more GDI handles are allocated by each RenderTargetBitmap and there is no reliable way to ensure they are cleaned up quickly once a RenderTargetBitmap goes out of scope.

According to the documentation there is a theoretical limit of 65,536 GDI handles per session and this is set to 10,000 per process in the registry.

I have hit the issue when doing lots of image tiling operations in a short time. I was able to work around it by forcing the garbage collector to clean up before there was a chance of hitting this limit. It is far from ideal as the call to GC.Collect() can incur a performance hit.

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