You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The dispatcher exit of SynchronizationContextAwaiter.IsCompleted in UtilitiesCS/Threading/UiThread.cs (lines 197-201 after the #816 hardening landed) carries the same null-reference-equals-null defect shape that #816 just fixed one exit above it: when the captured _dispatcher field is null and the executing thread also has no dispatcher, ReferenceEquals(Dispatcher.FromThread(Thread.CurrentThread), _dispatcher) compares null to null and evaluates true, so the accessor can return true for a thread that owns no UI dispatcher.
Command/flags used: N/A (found during code review, not via a failing run)
Data source or fixture: UtilitiesCS/Threading/UiThread.cs, reviewed on branch bug/uithread-iscompleted-branch2-residual-and-ac5-apartment-measurement-816 at commit b4941e252 during the issue Bug: uithread-iscompleted-branch2-residual-and-ac5-apartment-measurement #816 feature review (feature-audit dated 2026-09-14T00-40)
Steps to Reproduce
Call UiThread.Initialize() (or the equivalent capture path) such that it throws after the UI-thread-id field is assigned but before the _dispatcher field is assigned - i.e. between the UiThreadId assignment and the Dispatcher assignment in the initializer.
This leaves _uiThreadId set to a real thread id and _dispatcher at its default value of null.
From a thread whose managed thread id equals the captured _uiThreadId, but which owns no WPF dispatcher, await a DispatcherSynchronizationContext that belongs to a different thread than the one captured.
Evaluate SynchronizationContextAwaiter.IsCompleted on that awaiter.
Expected Behavior
The dispatcher exit should return false when the captured _dispatcher is null, because a null captured dispatcher means no UI dispatcher was ever actually captured and the awaiter should not report completion as if the executing thread were dispatcher-verified.
Actual Behavior
return _context is DispatcherSynchronizationContext && ReferenceEquals(Dispatcher.FromThread(Thread.CurrentThread), _dispatcher); - when _dispatcher is null and Dispatcher.FromThread(Thread.CurrentThread) is also null (the executing thread owns no dispatcher), ReferenceEquals(null, null) is true, so the whole expression can evaluate true given a matching DispatcherSynchronizationContext-typed ambient context. This is the identical defect shape that issue #816 hardened one exit above (the _uiSyncContext exit), where the delivered fix's own comment states the added null-dispatcher guard is "load-bearing, not redundant" for exactly this reason.
Logs / Screenshots
Attached minimal logs or screenshot
Snippet:
// UtilitiesCS/Threading/UiThread.cs, current dispatcher exit (post-#816):return_contextisDispatcherSynchronizationContext&&ReferenceEquals(System.Windows.Threading.Dispatcher.FromThread(Thread.CurrentThread),_dispatcher);
Summary
The dispatcher exit of
SynchronizationContextAwaiter.IsCompletedinUtilitiesCS/Threading/UiThread.cs(lines 197-201 after the #816 hardening landed) carries the same null-reference-equals-null defect shape that #816 just fixed one exit above it: when the captured_dispatcherfield is null and the executing thread also has no dispatcher,ReferenceEquals(Dispatcher.FromThread(Thread.CurrentThread), _dispatcher)compares null to null and evaluates true, so the accessor can return true for a thread that owns no UI dispatcher.Environment
UtilitiesCS/Threading/UiThread.cs, reviewed on branchbug/uithread-iscompleted-branch2-residual-and-ac5-apartment-measurement-816at commitb4941e252during the issue Bug: uithread-iscompleted-branch2-residual-and-ac5-apartment-measurement #816 feature review (feature-audit dated 2026-09-14T00-40)Steps to Reproduce
UiThread.Initialize()(or the equivalent capture path) such that it throws after the UI-thread-id field is assigned but before the_dispatcherfield is assigned - i.e. between theUiThreadIdassignment and theDispatcherassignment in the initializer._uiThreadIdset to a real thread id and_dispatcherat its default value of null._uiThreadId, but which owns no WPF dispatcher, await aDispatcherSynchronizationContextthat belongs to a different thread than the one captured.SynchronizationContextAwaiter.IsCompletedon that awaiter.Expected Behavior
The dispatcher exit should return
falsewhen the captured_dispatcheris null, because a null captured dispatcher means no UI dispatcher was ever actually captured and the awaiter should not report completion as if the executing thread were dispatcher-verified.Actual Behavior
return _context is DispatcherSynchronizationContext && ReferenceEquals(Dispatcher.FromThread(Thread.CurrentThread), _dispatcher);- when_dispatcheris null andDispatcher.FromThread(Thread.CurrentThread)is also null (the executing thread owns no dispatcher),ReferenceEquals(null, null)istrue, so the whole expression can evaluatetruegiven a matchingDispatcherSynchronizationContext-typed ambient context. This is the identical defect shape that issue #816 hardened one exit above (the_uiSyncContextexit), where the delivered fix's own comment states the added null-dispatcher guard is "load-bearing, not redundant" for exactly this reason.Logs / Screenshots
Impact / Severity
Source
From: docs/features/potential/2026-09-14-uithread-dispatcher-exit-null-dispatcher-referenceequals.md