Summary
UtilitiesCS.UiThread.SynchronizationContextAwaiter.IsCompleted decides whether an await context continuation may run inline by comparing the awaited SynchronizationContext to SynchronizationContext.Current by reference. For any context captured inside a WPF dispatcher operation (which is how every pooled ItemViewer is constructed, see issue #781), the captured instance is never the UI thread's ambient context again, so await viewer.UiSyncContext always reports not-completed and always posts, even when the caller is already on the UI thread. The result is an unnecessary message-queue hop on every such await rather than a failure.
Environment
- OS/version: Windows 11 Pro 10.0.26200, Outlook VSTO add-in, .NET Framework 4.8
- Python version: n/a
- Command/flags used: any
await someItemViewer.UiSyncContext (or await UiThread.UiSyncContext when that context was captured inside a dispatcher operation) executed on the UI thread
- Data source or fixture: n/a; reproducible with the runtime probe recorded in
docs/features/active/2026-09-05-breadcrumb-ui-boundary-guard-rejects-dispatcher-built-viewers-781/evidence/other/dispatcher-synccontext-probe.2026-09-05T10-40.md
Steps to Reproduce
- Construct an
ItemViewer through ItemViewerQueue.Dequeue (production path: ViewerQueueCore.CreateWithPriority inside UiThread.Dispatcher.Invoke), so viewer.UiSyncContext is a DispatcherSynchronizationContext.
- On the UI thread, outside any dispatcher operation (ambient context is the persistent
WindowsFormsSynchronizationContext), evaluate viewer.UiSyncContext.GetAwaiter().IsCompleted.
- Observe
false; await viewer.UiSyncContext therefore posts the continuation instead of continuing inline.
Expected Behavior
An await on the UI thread's own synchronization context should continue inline when the caller already runs on the owning thread, regardless of which SynchronizationContext instance happens to be ambient at the call site.
Actual Behavior
IsCompleted returns false whenever the ambient instance differs from the captured instance, so the continuation is always posted through SynchronizationContext.Post. No exception; one extra queued hop per await, and ordering relative to already-queued UI work changes.
Logs / Screenshots
Impact / Severity
Performance and ordering only; no functional failure. Recorded as out of scope by issue #781 and requested as a promoted follow-up by the #781 feature review (code-review.2026-09-05T17-29.md).
Source
From: docs/features/potential/2026-09-05-uithread-synccontext-awaiter-always-posts-for-dispatcher-built-viewers.md
Summary
UtilitiesCS.UiThread.SynchronizationContextAwaiter.IsCompleteddecides whether anawait contextcontinuation may run inline by comparing the awaitedSynchronizationContexttoSynchronizationContext.Currentby reference. For any context captured inside a WPF dispatcher operation (which is how every pooledItemVieweris constructed, see issue #781), the captured instance is never the UI thread's ambient context again, soawait viewer.UiSyncContextalways reports not-completed and always posts, even when the caller is already on the UI thread. The result is an unnecessary message-queue hop on every such await rather than a failure.Environment
await someItemViewer.UiSyncContext(orawait UiThread.UiSyncContextwhen that context was captured inside a dispatcher operation) executed on the UI threaddocs/features/active/2026-09-05-breadcrumb-ui-boundary-guard-rejects-dispatcher-built-viewers-781/evidence/other/dispatcher-synccontext-probe.2026-09-05T10-40.mdSteps to Reproduce
ItemViewerthroughItemViewerQueue.Dequeue(production path:ViewerQueueCore.CreateWithPriorityinsideUiThread.Dispatcher.Invoke), soviewer.UiSyncContextis aDispatcherSynchronizationContext.WindowsFormsSynchronizationContext), evaluateviewer.UiSyncContext.GetAwaiter().IsCompleted.false;await viewer.UiSyncContexttherefore posts the continuation instead of continuing inline.Expected Behavior
An
awaiton the UI thread's own synchronization context should continue inline when the caller already runs on the owning thread, regardless of whichSynchronizationContextinstance happens to be ambient at the call site.Actual Behavior
IsCompletedreturnsfalsewhenever the ambient instance differs from the captured instance, so the continuation is always posted throughSynchronizationContext.Post. No exception; one extra queued hop per await, and ordering relative to already-queued UI work changes.Logs / Screenshots
UtilitiesCS/Threading/UiThread.csline 100 (verified 2026-09-05):public bool IsCompleted => _context == SynchronizationContext.Current;.SynchronizationContextdoes not overload==, so this is a reference comparison. The probe cited above showsInvoke ctx == outer ambient : Falseon .NET Framework 4.8 STA.Impact / Severity
Performance and ordering only; no functional failure. Recorded as out of scope by issue #781 and requested as a promoted follow-up by the #781 feature review (
code-review.2026-09-05T17-29.md).Source
From: docs/features/potential/2026-09-05-uithread-synccontext-awaiter-always-posts-for-dispatcher-built-viewers.md