Async Profiler: Reuse V1 dispatcher across a method's suspensions.#130299
Async Profiler: Reuse V1 dispatcher across a method's suspensions.#130299lateralusX wants to merge 2 commits into
Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
|
Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices |
There was a problem hiding this comment.
Pull request overview
This PR changes async-profiler (asyncv1 / state-machine) dispatcher behavior so the same AsyncStateMachineDispatcher instance can be reused across multiple suspensions of the same boxed state machine, avoiding per-await dispatcher allocation and repeated Create events. It also preserves the “late-frame append” behavior on reuse and updates test invariants to match the new model.
Changes:
- Reuse the currently-active
AsyncStateMachineDispatcherwhen scheduling the same inner box, and reset per-resume callstack-walk state on re-entry. - Add an
AsyncProfiler.CreateAsyncContext.Appendhelper to flush late-frame async callstack append on the reuse path. - Update async-profiler tests to assert the new create/complete vs. resume/suspend balancing invariants.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs | Updates v1 tests’ balancing and ordering assertions for dispatcher reuse. |
| src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs | Updates shared assertion helper to reflect reuse semantics (creates == completes; resumes == completes + suspends). |
| src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDispatcher.cs | Adds reuse guard in CreateDispatcher and resets LastContinuation/ReachedLastContinuation per MoveNext entry; exposes InnerBox for reuse comparison. |
| src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncProfiler.cs | Adds CreateAsyncContext.Append to preserve late-frame append behavior when dispatcher allocation is skipped. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| // StateMachine dispatcher model with reuse: a single dispatcher spans all of a method's yields | ||
| // (it resumes/suspends multiple times) and is created + completed exactly once. So within a | ||
| // dispatcher tree every Create is balanced by exactly one Complete; Suspends are interior events. | ||
| private static void AssertCreateBalancesSuspendAndCompleteInChain(ParsedEventStream stream, ulong dispatcherId, string chainName) | ||
| { |
Summary
Optimizes the V1 (StateMachine) async profiler so a single AsyncStateMachineDispatcher spans all of a method's suspensions, instead of allocating a fresh dispatcher on every await/yield.
Motivation
In the previous model, each time a box's continuation was scheduled (i.e. each yield), CreateDispatcher allocated a new dispatcher wrapping the same box. A method that suspends N times produced N dispatchers and N Create events within the same tree. Since the box identity is stable across its suspensions, that per-yield allocation is avoidable: when the active dispatcher is already dispatching this exact box, we can reuse it. This matches asyncv2's behavior.
What changed
AsyncStateMachineDispatcher / CreateDispatcher:
Preserving the late-frame append on reuse (AsyncProfiler.CreateAsyncContext.Append):
Tests
Updated the shared balance assertions to the reuse invariant:
Outer dispatcher re-suspends before it completes (suspendIdx < completeIdx) rather than the old per-yield "re-suspending frame emits no Complete".