Skip to content

Cache continuation used for runtime async callable value task thunks#127973

Draft
jakobbotsch wants to merge 2 commits intodotnet:mainfrom
jakobbotsch:cache-value-task-source-continuation
Draft

Cache continuation used for runtime async callable value task thunks#127973
jakobbotsch wants to merge 2 commits intodotnet:mainfrom
jakobbotsch:cache-value-task-source-continuation

Conversation

@jakobbotsch
Copy link
Copy Markdown
Member

ValueTask backed by IValueTaskSource can suspend without allocating. However, runtime async did not support this when calling a ValueTask returning method through a runtime async callable thunk. In that case we would always allocate in the case where the ValueTask suspended.

This adds a custom ValueTaskContinuation that replaces the existing ValueTaskSourceNotifier mechanism. We now cache a ValueTaskContinuation instance and reuse it if possible whenever suspending for a ValueTask.

The same approach could be used to avoid allocations for runtime async callable thunks for task-returning methods.

cc @VSadov

`ValueTask` backed by `IValueTaskSource` can suspend without allocating.
However, runtime async did not support this when calling a `ValueTask`
returning method through a runtime async callable thunk. In that case we
would always allocate in the case where the `ValueTask` suspended.

This adds a custom `ValueTaskContinuation` that replaces the existing
`ValueTaskSourceNotifier` mechanism. We now cache a
`ValueTaskContinuation` instance and reuse it if possible whenever
suspending for a `ValueTask`.

The same approach could be used to avoid allocations for runtime async
callable thunks for task-returning methods.
Copilot AI review requested due to automatic review settings May 8, 2026 21:41
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates CoreCLR’s runtime-async callable thunk path for ValueTask/ValueTask<T> so that suspending on ValueTask backed by IValueTaskSource can reuse a cached continuation object rather than allocating per-suspension, and removes the older ValueTaskSourceNotifier / AsTaskOrNotifier mechanism.

Changes:

  • Introduces AsyncHelpers.TransparentAwaitValueTask* and a ValueTaskContinuation type that integrates with runtime-async suspension/resumption and is cached in TLS for reuse.
  • Removes ValueTask.AsTaskOrNotifier and the ValueTaskSourceNotifier helper type from ValueTask.
  • Adjusts VM continuation type handling to distinguish continuation subtypes that do have metadata (managed subclasses) vs the dynamically-created metadata-less ones, including DAC support.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs Removes AsTaskOrNotifier and deletes ValueTaskSourceNotifier.
src/coreclr/vm/vars.hpp Adds global g_singletonContinuationEEClass declaration for continuation metadata detection.
src/coreclr/vm/vars.cpp Adds global g_singletonContinuationEEClass definition.
src/coreclr/vm/typehandle.inl Avoids “upcasting” continuations that have real metadata.
src/coreclr/vm/typehandle.h Adds TypeHandle::IsContinuationWithMetadata().
src/coreclr/vm/typehandle.cpp Implements TypeHandle::IsContinuationWithMetadata() and relaxes class-object allocation restriction for metadata continuations.
src/coreclr/vm/methodtable.h Adds MethodTable::IsContinuationWithMetadata() and relaxes asserts/preconditions for metadata continuations.
src/coreclr/vm/methodtable.cpp Implements MethodTable::IsContinuationWithMetadata() and updates continuation-related special-casing.
src/coreclr/vm/corelib.h Removes binder entries for ValueTask.AsTaskOrNotifier; adds binder entries for TransparentAwaitValueTask*.
src/coreclr/vm/asyncthunks.cpp Emits thunk IL that tail-awaits via TransparentAwaitValueTask* instead of AsTaskOrNotifier + TransparentAwait.
src/coreclr/vm/asynccontinuations.cpp Moves singleton continuation EEClass storage to global g_singletonContinuationEEClass.
src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncThunks.cs Updates managed type-system thunk emission to call TransparentAwaitValueTask* and TailAwait.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs Adds ValueTaskContinuation, TLS caching, and TransparentAwaitValueTask*; updates suspension handling to use it.
src/coreclr/inc/dacvars.h Exposes g_singletonContinuationEEClass to DAC.
src/coreclr/debug/daccess/dacdbiimpl.cpp Updates continuation canonical-MT validity logic to account for metadata continuations.

Comment thread src/coreclr/vm/methodtable.h Outdated
}

inline BOOL IsContinuation();
BOOL IsContinuationWithMetadata();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably makes more sense to rename IsContinuation to IsContinuationWithoutMetadata and change the test since that's generally what we need to handle specially.


/// <summary>
/// Used by internal thunks that implement awaiting on Task or a ValueTask.
/// A ValueTask may wrap:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the comments about ValueTask may be dropped now.


// // Magic function which will suspend the current run of async methods
// AsyncHelpers.TransparentAwait(taskOrNotifier);
// TailAwait();
Copy link
Copy Markdown
Member

@VSadov VSadov May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not need to be in the same change, but I think the Task thunks can also TailAwait with a cached continuation inserted in TransparentAwait. Caching may not have the same level of benefits in Task case, but it feels like "why not".

@VSadov
Copy link
Copy Markdown
Member

VSadov commented May 9, 2026

Nice! Instead of caching and reusing ValueTaskSourceNotifier, we now cache/reuse the entire head continuation.

A scenario that awaits in a loop some ValueTaskSource wrapper (like reading chunks from a pipe) can run allocation-free.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants