dotnet / dotnet/wpf

Add Dispatcher.Created static event

Open
#7,301 12 comments 1 reaction 0 assignees View on GitHub
API suggestion
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

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.