Skip to content

TaggerMainThreadManager.PerformWorkOnMainThreadAsync allocates unnecessarily on the main-thread fast path #83998

@davidmikh

Description

@davidmikh

Summary

Internal VS telemetry identifies TaggerMainThreadManager.PerformWorkOnMainThreadAsync as a top allocation hotspot on the main thread during editor tag recomputation. The method allocates a TaskCompletionSource<TaggerUIData?> on every call, even when already on the main thread where the action executes synchronously. With multiple active taggers per document (classification, outlining, diagnostics, inheritance margin, etc.), each calling this method on every recompute cycle, the per-call allocation overhead (88-128 bytes) contributes to GC pressure on the UI thread during typing.

Current behavior

The method unconditionally allocates a new TaskCompletionSource<TaggerUIData?>() before checking IsOnMainThread. On the main-thread path, the action is executed synchronously and the TCS result is set immediately, but the allocation has already occurred.

Proposed fix

Split into a non-async fast path and an async slow path:

  • Fast path (on main thread): Run the action directly, return the result as a synchronous ValueTask — zero heap allocations.
  • Slow path (off main thread): Unchanged behavior — allocate TCS and enqueue for batched execution.

The method is intentionally kept non-async so the compiler doesn't generate a state machine for the fast path (async elision pattern, matching GetCompilationSlowAsync elsewhere in the repo).

Benchmark results

Method Before Mean After Mean CPU Reduction Before Alloc After Alloc Alloc Reduction
ReturnsNull 601.9 ns 345.2 ns -43% 88 B 0 B -100%
ReturnsValue 789.7 ns 544.1 ns -31% 128 B 40 B -69%

Benchmark: TaggerMainThreadManagerBenchmarks in src/Tools/IdeBenchmarks, exercising PerformWorkOnMainThreadAsync on the main thread with [CpuUsageDiagnoser].

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions