Add Dispatcher.Created static event
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
# The need
It's undesirable to instantiate a [`Dispatcher`](https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.cs) 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:
1. 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...
1. Use reflection to poll `Dispatcher._dispatchers` (while under the `Dispatcher._globalLock` guard of course)
1. Add a hook to intercept the call to the user32.dll > CreateWindowEx Win32 function call that happens while instantiating a dispatcher
1. Edit the CLR method descriptor for `Dispatcher.ctor` or `Dispatcher.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
1. Run a fork of WPF
# The proposal
```csharp
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();
```
Contributor guide
Assessment
This issue has not been assessed yet.