Description
The need
It's undesirable to instantiate a Dispatcher
on a non-UI thread. Doing so is (usually) a bug and leads to several issues, such as:
- Deadlock. Usually the dispatcher will not be running. But code may check if the current thread has a dispatcher and if so post a callback to it and wait to proceed until the callback has executed. Since the dispatcher is not running the callback never executes
- Memory leak. Usually the dispatcher will not be running. But code may post callbacks to it. Those callbacks contain state which is strongly-referenced by the dispatcher. That state graph will not be garbage collected until the thread ends
However it is extremely easy to instantiate a Dispatcher
on a non-UI thread. Broadly speaking there are two general ways this tends to happen:
- Access
Dispatcher.CurrentDispatcher
- Instantiate anything that derives from
DispatcherObject
It is usually possible for the disciplined solo programmer to avoid these issues in a small project. But sometimes people forget, and other times these issues are subtle and unexpected (e.g. I just recently learned that simply calling CommandManager.InvalidateRequerySuggested()
creates a dispatcher). So these issues tend to crop up in larger projects with many developers. As a result it's virtually guaranteed that the software project will periodically encounter deadlocks, memory leaks, and other unexpected behavior.
A solution
I'd like to solve this problem by giving developers a proactive way to know when a dispatcher is instantiated. Developers can write #if DEBUG
code that subscribes to the proposed event and inspects Thread.CurrentThread
in the handler, and then log the stack trace when it's an undesirable thread. Then developers will know exactly where the offending code is and will be able to solve it promptly.
Alternatives
I think this would be better than the alternatives. I only know of a few alternatives (I'm open to hearing of more). In increasing order of hackiness:
- Use the Win32 API to be notified after a Win32 window is created. I assume such a hook would execute asynchronously, so this would really be no better than # 2, which is...
- Use reflection to poll
Dispatcher._dispatchers
(while under theDispatcher._globalLock
guard of course) - Add a hook to intercept the call to the user32.dll > CreateWindowEx Win32 function call that happens while instantiating a dispatcher
- Edit the CLR method descriptor for
Dispatcher.ctor
orDispatcher.get_CurrentDispatcher
at runtime to point to my own hacked up method that does the same thing as this proposal but in a much messier way - Run a fork of WPF
The proposal
public sealed class Dispatcher
{
public static event DispatcherCreatedEventHandler? Created; // Add this event
public static Dispatcher CurrentDispatcher
{
get
{
Dispatcher currentDispatcher = FromThread(Thread.CurrentThread);;
if(currentDispatcher == null)
{
currentDispatcher = new Dispatcher();
//// The following line is new ////
Created?.Invoke();
//// The above line is new ////
}
return currentDispatcher;
}
}
}
// Add this delegate
public delegate void DispatcherCreatedEventHander();