Skip to content

Bug: uithread-synccontext-awaiter-always-posts-for-dispatcher-built-viewers #784

Description

@drmoisan
  • Work Mode: minor-audit

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

  1. Construct an ItemViewer through ItemViewerQueue.Dequeue (production path: ViewerQueueCore.CreateWithPriority inside UiThread.Dispatcher.Invoke), so viewer.UiSyncContext is a DispatcherSynchronizationContext.
  2. On the UI thread, outside any dispatcher operation (ambient context is the persistent WindowsFormsSynchronizationContext), evaluate viewer.UiSyncContext.GetAwaiter().IsCompleted.
  3. 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

  • Attached minimal logs or screenshot
  • Snippet: UtilitiesCS/Threading/UiThread.cs line 100 (verified 2026-09-05): public bool IsCompleted => _context == SynchronizationContext.Current;. SynchronizationContext does not overload ==, so this is a reference comparison. The probe cited above shows Invoke ctx == outer ambient : False on .NET Framework 4.8 STA.

Impact / Severity

  • Blocker
  • High
  • Medium
  • Low

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

Activity

  1. drmoisan commented on Sep 8, 2026

    @drmoisan
    OwnerAuthor

    Consolidated into #809 on 2026-09-07 (maintainer decision: batch review residuals by blast radius). All acceptance criteria from this issue are carried in #809; this issue is closed as superseded, not as fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions