8000 Support moving continuations to `SynchronizationContext`/`TaskScheduler` by jakobbotsch · Pull Request #117314 · dotnet/runtime · GitHub
[go: up one dir, main page]

Skip to content

Support moving continuations to SynchronizationContext/TaskScheduler #117314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 14, 2025

Conversation

jakobbotsch
Copy link
Member
@jakobbotsch jakobbotsch commented Jul 4, 2025

This adds support for moving continuations after task awaits to the right context:

  • For unconfigured task awaits or ConfigureAwait(true), continuations are posted to the captured SynchronizationContext or TaskScheduler by the BCL
  • For ConfigureAwait(false) continuations are moved to the thread pool

The JIT inserts a call to a new AsyncHelpers.CaptureContinuationContext when suspending because of a task await. That function either captures the current SynchronizationContext or TaskScheduler into the continuation in a known place. Later the BCL will fetch the context from the continuation object based on a flag to determine whether the continuation needs to be posted somewhere else or whether it can be inlined. There is a new Task-derived ThunkTask that replaces the existing C#-async-function-with-a-loop and which handles the continuations explicitly.

Inlining of continuations currently has no check on stack depth. This might be necessary to add. There are some differences compared to async 1 with how continuations are executed. In particular some common cases that resulted in unbounded stack usage with async 1 does not result in increasing stack usage with runtime async. Hence I am not 100% sure whether we need a mitigation or not, but more investigation is needed from my side. I'd like to leave that as a follow-up.

To get to the right behavior I have disabled inlining of task-awaited functions. This is a large concession, but getting the behavior right in the face of inlining proved to be quite tricky with the JIT's current internal representation. It is near top priority for me to reenable the inlining support, but I would like to work on it separately.

There is still one missing piece for correctness: saving and restoring the Thread._synchronizationContext field around async calls. It runs into similar trickiness when trying to represent the expected behavior (which is that the field is restored around synchronous calls, but not restored on resumption).

@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 4, 2025
Copy link
Contributor

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

Copy link
Contributor
@Copilot 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

Adds support for configuring where async continuations run—either on the captured SynchronizationContext/TaskScheduler or on the thread pool—by extending the JIT, VM, and BCL, and includes a new end-to-end test for SynchronizationContext behavior.

  • JIT & VM: Introduce a new JIT flag and enum to track “continue on captured context” vs. thread-pool, update inlining logic to fatal on unsupported sites, and inject calls to AsyncHelpers.CaptureContinuationContext.
  • BCL (AsyncHelpers): Add CaptureContinuationContext, expand the continuation flags enum, and implement ThunkTask/ThunkTaskCore to run continuations respecting the captured context.
  • Tests: New synchronization-context.cs verifies that continuations post to the custom context when appropriate.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/tests/async/synchronization-context/synchronization-context.csproj Add IL test project for sync-context tests
src/tests/async/synchronization-context/synchronization-context.cs New test verifying ConfigureAwait behavior with SynchronizationContext
src/coreclr/vm/jitinterface.cpp Expose new CaptureContinuationContext helper to the JIT
src/coreclr/vm/corelib.h Define new METHOD__ASYNC_HELPERS__CAPTURE_CONTINUATION_CONTEXT
src/coreclr/jit/inline.def Add CONTINUATION_HANDLING inline observation
src/coreclr/jit/importercalls.cpp Handle and dump continuation-context flags, block inlining
src/coreclr/jit/importer.cpp Wire up a new prefix flag for “continue on captured context”
src/coreclr/jit/gentree.h Introduce ContinuationContextHandling enum
src/coreclr/jit/compiler.h Add PREFIX_TASK_AWAIT_CONTINUE_ON_CAPTURED_CONTEXT flag
src/coreclr/jit/async.h Reserve a GC slot index for continuation context
src/coreclr/jit/async.cpp Allocate and populate continuation-context GC data and flags
src/coreclr/inc/corinfo.h Define new CorInfoContinuationFlags bits
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs Implement CaptureContinuationContext and ThunkTask runtime support
Comments suppressed due to low confidence (2)

src/tests/async/synchronization-context/synchronization-context.cs:51

  • Consider adding a test scenario that verifies continuation capture with a custom TaskScheduler (e.g., by setting TaskScheduler.Current to a non-default scheduler) to ensure the TaskScheduler branch in CaptureContinuationContext is exercised.
        Assert.Null(SynchronizationContext.Current);

src/coreclr/jit/importercalls.cpp:7900

  • The inline observation enum value is defined as CONTINUATION_HANDLING (in inline.def), not CALLSITE_CONTINUATION_HANDLING. You should use InlineObservation::CONTINUATION_HANDLING to avoid a compile error.
        inlineResult->NoteFatal(InlineObservation::CALLSITE_CONTINUATION_HANDLING);

@jakobbotsch
Copy link
Member Author

There are still a few TODOs, but I think this is ready for review. cc @dotnet/jit-contrib for the JIT parts and @VSadov for other parts. @stephentoub would you also take a look at the managed parts?

@jakobbotsch

This comment was marked as outdated.

WrappedYieldToThreadPool may be finished when TestSyncContext queues its
continuation on it in which case no post will happen.
@VSadov
Copy link
Member
VSadov commented Jul 11, 2025

There is still one missing piece for correctness: saving and restoring the Thread._synchronizationContext field around async calls. It runs into similar trickiness when trying to represent the expected behavior (which is that the field is restored around synchronous calls, but not restored on resumption).

Do you plan to add this in this PR?

@jakobbotsch
Copy link
Member Author

There is still one missing piece for correctness: saving and restoring the Thread._synchronizationContext field around async calls. It runs into similar trickiness when trying to represent the expected behavior (which is that the field is restored around synchronous calls, but not restored on resumption).

Do you plan to add this in this PR?

No, I want to do that in a follow-up.

Copy link
Member
@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks!

@jakobbotsch
Copy link
Member Author

@AndyAyersMS can you please look at the JIT parts?

@jakobbotsch jakobbotsch merged commit d46c447 into dotnet:main Jul 14, 2025
117 checks passed
@jakobbotsch jakobbotsch deleted the synchronization-context branch July 14, 2025 15:43

public object GetContinuationContext()
{
int index = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this (and similar) index calculations be simplified to int index = BitOperations.PopCount((uint)(Flags & (CorInfoContinuationFlags.CORINFO_CONTINUATION_RESULT_IN_GCDATA | CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION)))?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI runtime-async
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
0