Skip to content
This repository has been archived by the owner on May 2, 2018. It is now read-only.

Win32Exception is thrown when WPF runs out of internal resources #1

Open
tgjones opened this issue Jun 22, 2013 · 4 comments
Open

Win32Exception is thrown when WPF runs out of internal resources #1

tgjones opened this issue Jun 22, 2013 · 4 comments
Assignees
Labels
Milestone

Comments

@tgjones
Copy link
Owner

tgjones commented Jun 22, 2013

After an AppDomain has been up for a while, DynamicImage sometimes fails to generate an image because of an exception thrown by WPF:

System.ComponentModel.Win32Exception (0x80004005): The operation completed successfully
at MS.Win32.UnsafeNativeMethods.RegisterClassEx(WNDCLASSEX_D wc_d)
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.Media.MediaContextNotificationWindow..ctor(MediaContext ownerMediaContext)
at System.Windows.Media.MediaContext..ctor(Dispatcher dispatcher)
at System.Windows.Media.MediaContext.From(Dispatcher dispatcher)
at System.Windows.Media.Imaging.WriteableBitmap.SubscribeToCommittingBatch()
at System.Windows.Media.Imaging.WriteableBitmap.Unlock()
at System.Windows.Media.Imaging.WriteableBitmap.InitFromBitmapSource(BitmapSource source)
at System.Windows.Media.Imaging.WriteableBitmap..ctor(BitmapSource source)
at SoundInTheory.DynamicImage.Util.FastBitmap..ctor(String filename, UriKind uriKind)
at SoundInTheory.DynamicImage.Sources.FileImageSource.GetBitmap()
at SoundInTheory.DynamicImage.Layers.ImageLayer.CreateImage()
at SoundInTheory.DynamicImage.Layer.Process()
at SoundInTheory.DynamicImage.Composition.CreateImage()
at SoundInTheory.DynamicImage.Composition.GenerateImage()
at SoundInTheory.DynamicImage.Caching.ImageUrlGenerator.GetImageUrl(Composition composition)
at SoundInTheory.DynamicImage.Fluent.CompositionBuilder.get_Url()

This looks to be related to the Dispatcher creating internal HwndWrapper objects. There is an SO question and answer here, which suggest that Dispatchers need to be cleaned up:

http://stackoverflow.com/questions/8995588/asp-net-throws-win32exception-when-creating-bitmapimage-cannot-find-file-specif

DynamicImage doesn't explicitly create threads, so there is nowhere obvious to plug that code in. Perhaps DynamicImage could maintain its own thread pool and do all image generation on one of those threads.

@ghost ghost assigned tgjones Jun 22, 2013
@tgjones
Copy link
Owner Author

tgjones commented Aug 20, 2013

Here is another manifestation of what I believe is the same bug:

System.ComponentModel.Win32Exception (0x80004005): An attempt was made to reference a token that does not exist
   at MS.Win32.UnsafeNativeMethods.RegisterClassEx(WNDCLASSEX_D wc_d)
   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 MS.Win32.MessageOnlyHwndWrapper..ctor()
   at System.Windows.Threading.Dispatcher..ctor()
   at System.Windows.Threading.Dispatcher.get_CurrentDispatcher()
   at System.Windows.Threading.DispatcherObject..ctor()
   at System.Windows.DependencyObject..ctor()
   at System.Windows.Freezable..ctor()
   at System.Windows.Media.Animation.Animatable..ctor()
   at System.Windows.Media.ImageSource..ctor()
   at System.Windows.Media.Imaging.BitmapSource..ctor(Boolean useVirtuals)
   at System.Windows.Media.Imaging.BitmapImage..ctor(Uri uriSource, RequestCachePolicy uriCachePolicy)
   at System.Windows.Media.Imaging.BitmapImage..ctor(Uri uriSource)
   at SoundInTheory.DynamicImage.Util.FastBitmap..ctor(String filename, UriKind uriKind)
   at SoundInTheory.DynamicImage.Sources.FileImageSource.GetBitmap()
   at SoundInTheory.DynamicImage.Layers.ImageLayer.CreateImage()
   at SoundInTheory.DynamicImage.Layer.Process()
   at SoundInTheory.DynamicImage.Composition.CreateImage()
   at SoundInTheory.DynamicImage.Composition.GenerateImage()
   at SoundInTheory.DynamicImage.Caching.ImageUrlGenerator.GetImageUrl(Composition composition)
   at SoundInTheory.DynamicImage.Fluent.CompositionBuilder.get_Url()

@drub0y
Copy link

drub0y commented Aug 26, 2013

This library is great, but using it from any random thread is simply not possible because of it's dependency on WPF components. WPF components must be run from a thread that is properly initialized as STA and running a proper dispatcher message pump due to the way they schedule themselves for resource cleanup.

I have explained this before on StackOverflow and provided an implementation of a TaskScheduler subclass that allows you to leverage the TPL to execute this kind of work on the proper threads nicely. I even recently updated that implementation to work gracefully with the new .NET 4.5 async/await SynchronizationContext based features.

In the end, this library will either need to hide these details by executing the work on its own internally managed Dispatcher thread pool and simply handing back Tasks that the caller can wait on, or it can pass that burden onto the caller. I would suggest the former, but it will require changes to the API to return Tasks that can be awaited. I don't think this is a radical change in today's landscape since almost anyone doing anything with .NET 4.5 is probably working with async APIs now.

Great API, so I wish you luck with the changes! Let me know if you'd like any further details on the subject.

@tgjones
Copy link
Owner Author

tgjones commented Aug 26, 2013

That's awesome, thank you - you've filled in some of the blanks in my understanding of WPF and threading.

(By the way, the link to your Gist from your SO answer is broken. I assume it should link to this.)

What do you think of this answer in the SO question you linked to? If I understand correctly, without using a thread initialized as STA, calling InvokeShutdown() still might not clean things up properly?

If I wanted to keep the DynamicImage API exactly as-is, do you see a problem with me using your DispatcherTaskScheduler internally, and wait on Tasks as necessary, before returning to the caller? It might not be ideal, but it wouldn't be any worse than it is now, where everything happens synchronously anyway.

The only problem I can see is in the cases where the DynamicImage API does expose WPF objects externally, such as here and here - I'd need to investigate whether a BitmapSource can be used on a different thread from the one that created it.

@drub0y
Copy link

drub0y commented Aug 26, 2013

First, thanks for the heads up, I've fixed the Gist link on SO.

The problem with simply calling Dispatcher::InvokeShutdown is amount of overhead you're going to incur spinning up and tearing down the Dispatcher infrastructure on the threads each time. It's not cheap at all. Plus there's no guarantees it's going to continue to work correctly across framework releases because technically InvokeShutdown posts a message to the Dispatcher queue using Invoke, but because it's sent with a priority of Send and because of the way some of the Dispatcher's internals work it just so happens it will execute immediately.

As for waiting on the Task internally, that does hide the complexity, but you will be sacrificing the threads of your caller by not exposing it as an async API. Naturally you could do this for now and just look to create async friendly APIs in vNext of your API. At least this way your API is not leaking any more and you still cleanly abstract the details of the context switching from callers.

As for handing back BitmapSources, they are "freezable" types in WPF. So just make sure you call BitmapSource::Freeze before handing them back to the caller. This would be a little bit of a breaking change if your callers were expecting to be able to modify the BitmapSource once they got it back, but obviously if they're not doing this on a Dispatcher thread of their own they'd be having their own leaks anyway. So I think you solve that with documentation and samples.

tgjones pushed a commit that referenced this issue Sep 8, 2013
Images are created on a thread pool that is controlled by
DispatcherTaskScheduler. The Dispatcher infrastructure is only started
on these threads, whereas previously the Dispatcher infrastructure would
have been started on all ASP.NET that requested DynamicImage to create
an image. This is a possible fix for #1, but needs more testing. It also
involves several breaking API changes, so a decision will have to be
made whether to stick to SemVer and release this as v4.0.0.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants