UWP: static XAML events subscribed from a secondary CoreApplication view are invoked on the first subscriber's thread
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 665
- Forks
- 134
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 32
Description
Summary
In a UWP app on modern .NET (<UseUwp>true</UseUwp>), a handler for a static XAML event subscribed from a secondary CoreApplication view is invoked on the first view that subscribed, not on the view that subscribed it.
CompositionTarget.Rendering is the clearest case: both views' handlers are driven by a single registration, so the secondary view's frame callback arrives on the main view's thread. Under .NET Native the handler ran on the subscribing view's thread.
Repro
protected override async void OnLaunched(LaunchActivatedEventArgs e)
{
_mainThreadId = Environment.CurrentManagedThreadId;
Window.Current.Content = new Grid();
Window.Current.Activate();
CompositionTarget.Rendering += OnMainRendering;
var newView = CoreApplication.CreateNewView();
int viewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
_secondaryThreadId = Environment.CurrentManagedThreadId;
Window.Current.Content = new Grid();
Window.Current.Activate();
viewId = ApplicationView.GetForCurrentView().Id;
CompositionTarget.Rendering += OnSecondaryRendering; // subscribed on this view's thread
});
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId);
}
// each handler records Environment.CurrentManagedThreadId and increments a counter
Result
main thread 4 ticks 256
secondary thread 5 ticks 256
Rendering handlers ran on: main 4, secondary 4 <-- both on the main view's thread
Two independent view render loops would not stay in lockstep; the tick counts are exactly equal on every run (256/256, 258/258, 262/262), which is consistent with both delegates being invoked from one registration rather than one per view.
The ABI allows what the projection does not
Fetching the statics directly, on each view's thread:
RoGetActivationFactory("Windows.UI.Xaml.Media.CompositionTarget", IID_ICompositionTargetStatics)
main 0x25B31BA1878
secondary 0x25B31BA1878 (same object)
QueryInterface(IAgileObject) -> S_OK (agile, so a call from either view runs on the calling thread)
So the statics object is a process-wide agile singleton, and add_Rendering invoked from the secondary view's thread would register against that view. The aggregation appears to happen above the ABI, in the per-statics event source cache: the second += does not produce a second add_Rendering, it appends the delegate to the registration the first view already made.
Consistent with that, the generated projection caches the statics in a plain static field (__objRef_global__Windows_UI_Xaml_Media_ICompositionTargetStatics), and Microsoft.Windows.UI.Xaml.dll contains no ThreadStatic/ThreadLocal anywhere.
Impact
Anything frame-driven in a secondary view runs on the wrong thread. Windows.UI.Composition objects tolerate it because they are agile, which masks the problem; XAML objects do not. In our app the visible symptom is that image animations in a secondary window freeze, because the handler ends up calling WriteableBitmap.Invalidate() on a bitmap created on the other view:
System.Runtime.InteropServices.COMException (0x8001010E) // RPC_E_WRONG_THREAD
at ABI.Windows.UI.Xaml.Media.Imaging.IWriteableBitmapMethods.Invalidate(IObjectReference _obj)
at Windows.UI.Xaml.Media.Imaging.WriteableBitmap.Invalidate()
Any per-view static WinRT event is affected, not just this one.
Environment
- Windows 11 10.0.26200
- .NET SDK 10.0.302,
net10.0-windows10.0.26100.0,UseUwp=true, x64, self-contained, no AOT - Microsoft.Windows.SDK.NET.Ref projection 10.0.26100.55
- Microsoft.Windows.CsWinRT 2.2.0 referenced by the app
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the generated projection's plain static __objRef_global__Windows_UI_Xaml_Media_ICompositionTargetStatics cache and the static event add/remove path. Reproduce the two CoreApplication views and compare registration and callback threads; done means each view's handler is registered and invoked on its subscribing view's thread without regressing the reported behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- desktop, operating-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100