dotnet / dotnet/wpf

Dispatcher lifetime: why non-UI threads can't be used to safely create frozen Freezable objects

Open
#3,412 4 comments 4 reactions 0 assignees View on GitHub
Design Discussion
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

Every single DispatcherObject takes a reference to Dispatcher.CurrentDispatcher, even objects destined to be frozen (which sets the Dispatcher property back to null). Every access of Dispatcher.CurrentDispatcher will unconditionally create a Dispatcher if one wasn't created for the current thread. Dispatcher creates an HWND and other resources that will never get freed if the Dispatcher is created on a thread pool thread, because (a) a strong reference to the Dispatcher is saved in a List<> instead of a [ThreadStatic], and (b) it receives no shutdown notification because no message pump was ever running in the first place. Therefore you have these orphaned object graphs that can even cause native UI resource exhaustion and Win32Exceptions when too many get created.

In short, if you try to load a BitmapSource from a file on a thread pool thread, you're going to leak a Dispatcher even if you freeze the object. Open Spy++ some time and see all the things you leak.

There doesn't appear to be semantics for constructing Freezables without a Dispatcher, but for now there doesn't need to be. There are two parts to fixing this with zero disruption to existing application code:

1. Don't allocate message pump or rendering state like MediaContext, etc until actually used. Maybe do something similar to what was done to Task in the TPL: put all the extra state into an isolated state class that at minimum is only the cost of one null reference.

2. Make each Dispatcher instance a [ThreadStatic] reference and eliminate the global list of Dispatchers. This way they can be GC'd easily on thread termination even if no shutdown notification ever happens. If Dispatchers need to be discoverable, the active ones can reveal themselves when they need to call into render services, etc, and then they should only be placed in a locked weak collection.

Optionally, add an IsDispatcherCreated() method to know if a Dispatcher was created yet for the current thread. This will allow application programmers to make more sensitive decisions about whether to take UI-related actions on the current thread.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.