Support moving continuations to SynchronizationContext
/TaskScheduler
#117314
+573
−123
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This adds support for moving continuations after task awaits to the right context:
ConfigureAwait(true)
, continuations are posted to the capturedSynchronizationContext
orTaskScheduler
by the BCLConfigureAwait(false)
continuations are moved to the thread poolThe JIT inserts a call to a new
AsyncHelpers.CaptureContinuationContext
when suspending because of a task await. That function either captures the currentSynchronizationContext
orTaskScheduler
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 newTask
-derivedThunkTask
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).