SystemEvents can contain reference to invalid thread and InvalidAsynchronousStateException
- Dominant language
- C#
- Stars
- 4.9k
- Forks
- 1.1k
- Avg merge
- 20h 23m
- Merged PRs (30d)
- 103
Description
### .NET version
Net48, Net8
### Did it work in .NET Framework?
No
### Did it work in any of the earlier releases of .NET Core or .NET 5+?
_No response_
### Issue description
### Short:
Potential application crash by SystemEvents having a reference to an invalid thread used as a SynchronizationContext.
### Long:
Microsoft.Win32.SystemEvents has a few different static events that winforms controls can register with.
Examples are:
- UserPreferenceChanged
- DisplaySettingsChanged
Internally it is not a pure list of delegates. The SystemEventInvokeInfo contains a delegate and an optional SynchronizationContext.
The SynchronizationContext is of two types
- System.Threading.SynchronizationContext
- System.Windows.Forms.WindowsFormsSynchronizationContext
The SynchronizationContext can contain a thread, or the thread can be null.
In the constructor, the thread is requested by System.ComponentModel.AsyncOperationManager.SynchronizationContext
So whenever a display setting in Windows changes, the DisplaySettingsChanged event gets fired and the delegates get invoked with a potential to be invoked with a WindowsFormsSynchronizationContext.
The issue:
These static events are not only used by instances of controls that register and deregister on disposal.
There are also static types that register and they only register once for that type. Examples are:
- System.Windows.Forms.Screen
- System.Drawing.Internal.SystemColorTracker
- System.Windows.Forms.ProfessionalColors
- System.Windows.Forms.SystemInforation
And when doing that they create a SynchronizationContext that depends on the thread and the state of the thread.
- Start/Main Thread in Program.Main()
- Start/Main Thread within System.Windows.Forms.Application.Run()
- Any other thread
And while the main thread will stay for the lifetime of the application, other threads may not.
Result:
If a thread other than the main thread happens to read from one of the static types like
requesting System.Windows.Forms.Screen.PrimaryScreen.WorkingArea before the main thread does,
it creates a SynchronizationContext with a dead thread and that can throw an exception later and crash the application when a Windows setting changes that triggers the event.
InvalidAsynchronousStateException(SR.ThreadNoLongerValid) from
Stacktrace:
```
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state) Line 84 C#
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization, object[] args) Line 34 C#
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization, object key, object[] args) Line 844 C#
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.RaiseEvent(object key, object[] args) Line 819 C#
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.OnUserPreferenceChanging(int msg, nint wParam, nint lParam) Line 809 C#
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.WindowProc(nint hWnd, int msg, nint wParam, nint lParam) Line 955 C#
[Native to Managed Transition]
[Managed to Native Transition]
Microsoft.Win32.SystemEvents.dll!Interop.User32.DispatchMessageW(ref Interop.User32.MSG msg) Line 859 C#
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.WindowThreadProc() Line 1033 C#
```
Related Code files:
- Link: SystemEvents Class https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
- Link: SystemEvents s_handlers https://github.com/dotnet/runtime/blob/e0620bc380fa291867568e653206a9ecb8923d64/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs#L62
- Link: SystemEventInvokeInfo: https://github.com/dotnet/runtime/blob/e0620bc380fa291867568e653206a9ecb8923d64/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs#L1246
### Steps to reproduce
This demo application an enforce the behaviour that would otherwise be possible by accident.
It is a mini version of the application where it actually happened and can be used to simulate how the event handlers are setup by changing the injection point in Program.Main.
In this case, it is the SplashScreen-Form on a separate thread that causes the problem because it accesses System.Windows.Forms.Screen before the ui thread does.
While in this specific case, it can be avoided now that i know what causes it.
In general, a harmless looking read access to a property (like System.Windows.Forms.Screen.PrimaryScreen.WorkingArea) can link a thread permanently to static types.
[DeadEventHandler_V1.zip](https://github.com/user-attachments/files/20723170/DeadEventHandler_V1.zip)
Contributor guide
Assessment
This issue has not been assessed yet.