diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs
index 4ec931c4af31..0784e6135c83 100644
--- a/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs
+++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ConfiguredValueTaskAwaitable.cs
@@ -39,7 +39,7 @@ public struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion
/// The value being awaited.
private ValueTask _value; // Methods are called on this; avoid making it readonly so as to avoid unnecessary copies
/// The value to pass to ConfigureAwait.
- private readonly bool _continueOnCapturedContext;
+ internal readonly bool _continueOnCapturedContext;
/// Initializes the awaiter.
/// The value to be awaited.
@@ -66,6 +66,9 @@ public void OnCompleted(Action continuation) =>
/// Schedules the continuation action for the .
public void UnsafeOnCompleted(Action continuation) =>
_value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation);
+
+ /// Gets the task underlying .
+ internal Task AsTask() => _value.AsTask();
}
}
}
diff --git a/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs
index c4194825217a..30e688e07711 100644
--- a/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs
+++ b/src/mscorlib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs
@@ -33,5 +33,8 @@ public void OnCompleted(Action continuation) =>
/// Schedules the continuation action for this ValueTask.
public void UnsafeOnCompleted(Action continuation) =>
_value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().UnsafeOnCompleted(continuation);
+
+ /// Gets the task underlying .
+ internal Task AsTask() => _value.AsTask();
}
}
diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
index 704628690a48..e8e0d60202f1 100644
--- a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
+++ b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
@@ -365,7 +365,7 @@ public void AwaitOnCompleted(
{
try
{
- awaiter.OnCompleted(GetMoveNextDelegate(ref stateMachine));
+ awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
}
catch (Exception e)
{
@@ -384,10 +384,107 @@ public void AwaitUnsafeOnCompleted(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine
+ {
+ IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
+
+ // TODO https://github.com/dotnet/coreclr/issues/12877:
+ // Once the JIT is able to recognize "awaiter is ITaskAwaiter" and "awaiter is IConfiguredTaskAwaiter",
+ // use those in order to a) consolidate a lot of this code, and b) handle all Task/Task and not just
+ // the few types special-cased here. For now, handle common {Configured}TaskAwaiter. Having the types
+ // explicitly listed here allows the JIT to generate the best code for them; otherwise we'll fall through
+ // to the later workaround.
+ if (typeof(TAwaiter) == typeof(TaskAwaiter) ||
+ typeof(TAwaiter) == typeof(TaskAwaiter) ||
+ typeof(TAwaiter) == typeof(TaskAwaiter) ||
+ typeof(TAwaiter) == typeof(TaskAwaiter) ||
+ typeof(TAwaiter) == typeof(TaskAwaiter) ||
+ typeof(TAwaiter) == typeof(TaskAwaiter) ||
+ typeof(TAwaiter) == typeof(TaskAwaiter) ||
+ typeof(TAwaiter) == typeof(TaskAwaiter))
+ {
+ ref TaskAwaiter ta = ref Unsafe.As(ref awaiter); // relies on TaskAwaiter/TaskAwaiter having the same layout
+ TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
+ }
+ else if (
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
+ typeof(TAwaiter) == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter))
+ {
+ ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As(ref awaiter);
+ TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
+ }
+
+ // Handle common {Configured}ValueTaskAwaiter types. Unfortunately these need to be special-cased
+ // individually, as we don't have good way to extract the task from a ValueTaskAwaiter when we don't
+ // know what the T is; we could make ValueTaskAwaiter implement an IValueTaskAwaiter interface, but
+ // calling a GetTask method on that would end up boxing the awaiter. This hard-coded list here is
+ // somewhat arbitrary and is based on types currently in use with ValueTask in coreclr/corefx.
+ else if (typeof(TAwaiter) == typeof(ValueTaskAwaiter))
+ {
+ var vta = (ValueTaskAwaiter)(object)awaiter;
+ TaskAwaiter.UnsafeOnCompletedInternal(vta.AsTask(), box, continueOnCapturedContext: true);
+ }
+ else if (typeof(TAwaiter) == typeof(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter))
+ {
+ var vta = (ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter)(object)awaiter;
+ TaskAwaiter.UnsafeOnCompletedInternal(vta.AsTask(), box, vta._continueOnCapturedContext);
+ }
+ else if (typeof(TAwaiter) == typeof(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter))
+ {
+ var vta = (ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter)(object)awaiter;
+ TaskAwaiter.UnsafeOnCompletedInternal(vta.AsTask(), box, vta._continueOnCapturedContext);
+ }
+ else if (typeof(TAwaiter) == typeof(ConfiguredValueTaskAwaitable>.ConfiguredValueTaskAwaiter))
+ {
+ var vta = (ConfiguredValueTaskAwaitable>.ConfiguredValueTaskAwaiter)(object)awaiter;
+ TaskAwaiter.UnsafeOnCompletedInternal(vta.AsTask(), box, vta._continueOnCapturedContext);
+ }
+ else if (typeof(TAwaiter) == typeof(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter))
+ {
+ var vta = (ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter)(object)awaiter;
+ TaskAwaiter.UnsafeOnCompletedInternal(vta.AsTask(), box, vta._continueOnCapturedContext);
+ }
+
+ // To catch all Task/Task awaits, do the currently more expensive interface checks.
+ // Eventually these and the above Task/Task checks should be replaced by "is" checks,
+ // once that's recognized and optimized by the JIT. We do these after all of the hardcoded
+ // checks above so that they don't incur the costs of these checks.
+ else if (InterfaceIsCheckWorkaround.IsITaskAwaiter)
+ {
+ ref TaskAwaiter ta = ref Unsafe.As(ref awaiter);
+ TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
+ }
+ else if (InterfaceIsCheckWorkaround.IsIConfiguredTaskAwaiter)
+ {
+ ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As(ref awaiter);
+ TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
+ }
+
+ // The awaiter isn't specially known. Fall back to doing a normal await.
+ else
+ {
+ // TODO https://github.com/dotnet/coreclr/issues/14177:
+ // Move the code back into this method once the JIT is able to
+ // elide it successfully when one of the previous branches is hit.
+ AwaitArbitraryAwaiterUnsafeOnCompleted(ref awaiter, box);
+ }
+ }
+
+ /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
+ /// Specifies the type of the awaiter.
+ /// The awaiter.
+ /// The state machine box.
+ private static void AwaitArbitraryAwaiterUnsafeOnCompleted(ref TAwaiter awaiter, IAsyncStateMachineBox box)
+ where TAwaiter : ICriticalNotifyCompletion
{
try
{
- awaiter.UnsafeOnCompleted(GetMoveNextDelegate(ref stateMachine));
+ awaiter.UnsafeOnCompleted(box.MoveNextAction);
}
catch (Exception e)
{
@@ -399,7 +496,7 @@ public void AwaitUnsafeOnCompleted(
/// Specifies the type of the async state machine.
/// The state machine.
/// The "boxed" state machine.
- private Action GetMoveNextDelegate(
+ private IAsyncStateMachineBox GetStateMachineBox(
ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine
{
@@ -416,7 +513,7 @@ private Action GetMoveNextDelegate(
{
stronglyTypedBox.Context = currentContext;
}
- return stronglyTypedBox.MoveNextAction;
+ return stronglyTypedBox;
}
// The least common case: we have a weakly-typed boxed. This results if the debugger
@@ -440,7 +537,7 @@ private Action GetMoveNextDelegate(
// Update the context. This only happens with a debugger, so no need to spend
// extra IL checking for equality before doing the assignment.
weaklyTypedBox.Context = currentContext;
- return weaklyTypedBox.MoveNextAction;
+ return weaklyTypedBox;
}
// Alert a listening debugger that we can't make forward progress unless it slips threads.
@@ -462,34 +559,33 @@ private Action GetMoveNextDelegate(
m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
box.StateMachine = stateMachine;
box.Context = currentContext;
- return box.MoveNextAction;
+ return box;
}
/// A strongly-typed box for Task-based async state machines.
/// Specifies the type of the state machine.
/// Specifies the type of the Task's result.
private sealed class AsyncStateMachineBox :
- Task, IDebuggingAsyncStateMachineAccessor
+ Task, IAsyncStateMachineBox
where TStateMachine : IAsyncStateMachine
{
/// Delegate used to invoke on an ExecutionContext when passed an instance of this box type.
private static readonly ContextCallback s_callback = s => ((AsyncStateMachineBox)s).StateMachine.MoveNext();
/// A delegate to the method.
- public readonly Action MoveNextAction;
+ private Action _moveNextAction;
/// The state machine itself.
public TStateMachine StateMachine; // mutable struct; do not make this readonly
/// Captured ExecutionContext with which to invoke ; may be null.
public ExecutionContext Context;
- public AsyncStateMachineBox()
- {
- var mn = new Action(MoveNext);
- MoveNextAction = AsyncCausalityTracer.LoggingOn ? AsyncMethodBuilderCore.OutputAsyncCausalityEvents(this, mn) : mn;
- }
+ /// A delegate to the method.
+ public Action MoveNextAction =>
+ _moveNextAction ??
+ (_moveNextAction = AsyncCausalityTracer.LoggingOn ? AsyncMethodBuilderCore.OutputAsyncCausalityEvents(this, new Action(MoveNext)) : new Action(MoveNext));
- /// Call MoveNext on .
- private void MoveNext()
+ /// Calls MoveNext on
+ public void MoveNext()
{
if (Context == null)
{
@@ -501,8 +597,19 @@ private void MoveNext()
}
}
+ ///
+ /// Calls MoveNext on . Implements ITaskCompletionAction.Invoke so
+ /// that the state machine object may be queued directly as a continuation into a Task's
+ /// continuation slot/list.
+ ///
+ /// The completing task that caused this method to be invoked, if there was one.
+ void ITaskCompletionAction.Invoke(Task completedTask) => MoveNext();
+
+ /// Signals to Task's continuation logic that runs arbitrary user code via MoveNext.
+ bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true;
+
/// Gets the state machine as a boxed object. This should only be used for debugging purposes.
- IAsyncStateMachine IDebuggingAsyncStateMachineAccessor.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging
+ IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging
}
/// Gets the for this builder.
@@ -815,12 +922,24 @@ internal static Task CreateCacheableTask(TResult result) =>
new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
}
+ /// Temporary workaround for https://github.com/dotnet/coreclr/issues/12877.
+ internal static class InterfaceIsCheckWorkaround
+ {
+ internal static readonly bool IsITaskAwaiter = typeof(TAwaiter).GetInterface("ITaskAwaiter") != null;
+ internal static readonly bool IsIConfiguredTaskAwaiter = typeof(TAwaiter).GetInterface("IConfiguredTaskAwaiter") != null;
+ }
+
///
- /// An interface implemented by to allow access
- /// non-generically to state associated with a builder and state machine.
+ /// An interface implemented by all instances, regardless of generics.
///
- interface IDebuggingAsyncStateMachineAccessor
+ interface IAsyncStateMachineBox : ITaskCompletionAction
{
+ ///
+ /// Gets an action for moving forward the contained state machine.
+ /// This will lazily-allocate the delegate as needed.
+ ///
+ Action MoveNextAction { get; }
+
/// Gets the state machine as a boxed object. This should only be used for debugging purposes.
IAsyncStateMachine GetStateMachineObject();
}
@@ -843,7 +962,7 @@ internal static Action TryGetStateMachineForDebugger(Action action) // debugger
{
object target = action.Target;
return
- target is IDebuggingAsyncStateMachineAccessor sm ? sm.GetStateMachineObject().MoveNext :
+ target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
action;
}
diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs b/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs
index 93bedd62b6f3..ce8867773037 100644
--- a/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs
+++ b/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs
@@ -56,10 +56,13 @@ namespace System.Runtime.CompilerServices
{
/// Provides an awaiter for awaiting a .
/// This type is intended for compiler use only.
- public struct TaskAwaiter : ICriticalNotifyCompletion
+ public struct TaskAwaiter : ICriticalNotifyCompletion, ITaskAwaiter
{
+ // WARNING: Unsafe.As is used to access the generic TaskAwaiter<> as TaskAwaiter.
+ // Its layout must remain the same.
+
/// The task being awaited.
- private readonly Task m_task;
+ internal readonly Task m_task;
/// Initializes the .
/// The to be awaited.
@@ -212,6 +215,30 @@ internal static void OnCompletedInternal(Task task, Action continuation, bool co
task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext);
}
+ /// Schedules the continuation onto the associated with this .
+ /// The task being awaited.
+ /// The action to invoke when the await operation completes.
+ /// Whether to capture and marshal back to the current context.
+ /// Whether to flow ExecutionContext across the await.
+ /// The argument is null (Nothing in Visual Basic).
+ /// The awaiter was not properly initialized.
+ /// This method is intended for compiler user rather than use directly in code.
+ internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
+ {
+ Debug.Assert(stateMachineBox != null);
+
+ // If TaskWait* ETW events are enabled, trace a beginning event for this await
+ // and set up an ending event to be traced when the asynchronous await completes.
+ if (TplEtwProvider.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
+ {
+ task.SetContinuationForAwait(OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction), continueOnCapturedContext, flowExecutionContext: false);
+ }
+ else
+ {
+ task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext);
+ }
+ }
+
///
/// Outputs a WaitBegin ETW event, and augments the continuation action to output a WaitEnd ETW event.
///
@@ -290,8 +317,11 @@ private static Action OutputWaitEtwEvents(Task task, Action continuation)
/// Provides an awaiter for awaiting a .
/// This type is intended for compiler use only.
- public struct TaskAwaiter : ICriticalNotifyCompletion
+ public struct TaskAwaiter : ICriticalNotifyCompletion, ITaskAwaiter
{
+ // WARNING: Unsafe.As is used to access TaskAwaiter<> as the non-generic TaskAwaiter.
+ // Its layout must remain the same.
+
/// The task being awaited.
private readonly Task m_task;
@@ -343,6 +373,20 @@ public TResult GetResult()
}
}
+ ///
+ /// Marker interface used to know whether a particular awaiter is either a
+ /// TaskAwaiter or a TaskAwaiter`1. It must not be implemented by any other
+ /// awaiters.
+ ///
+ internal interface ITaskAwaiter { }
+
+ ///
+ /// Marker interface used to know whether a particular awaiter is either a
+ /// CTA.ConfiguredTaskAwaiter or a CTA`1.ConfiguredTaskAwaiter. It must not
+ /// be implemented by any other awaiters.
+ ///
+ internal interface IConfiguredTaskAwaiter { }
+
/// Provides an awaitable object that allows for configured awaits on .
/// This type is intended for compiler use only.
public struct ConfiguredTaskAwaitable
@@ -370,12 +414,15 @@ public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter()
/// Provides an awaiter for a .
/// This type is intended for compiler use only.
- public struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion
+ public struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion, IConfiguredTaskAwaiter
{
+ // WARNING: Unsafe.As is used to access the generic ConfiguredTaskAwaiter as this.
+ // Its layout must remain the same.
+
/// The task being awaited.
- private readonly Task m_task;
+ internal readonly Task m_task;
/// Whether to attempt marshaling back to the original context.
- private readonly bool m_continueOnCapturedContext;
+ internal readonly bool m_continueOnCapturedContext;
/// Initializes the .
/// The to await.
@@ -456,8 +503,11 @@ public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter()
/// Provides an awaiter for a .
/// This type is intended for compiler use only.
- public struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion
+ public struct ConfiguredTaskAwaiter : ICriticalNotifyCompletion, IConfiguredTaskAwaiter
{
+ // WARNING: Unsafe.As is used to access this as the non-generic ConfiguredTaskAwaiter.
+ // Its layout must remain the same.
+
/// The task being awaited.
private readonly Task m_task;
/// Whether to attempt marshaling back to the original context.
diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs
index 84811fb06835..732f26141c3d 100644
--- a/src/mscorlib/src/System/Threading/Tasks/Task.cs
+++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs
@@ -2627,6 +2627,57 @@ internal void SetContinuationForAwait(
}
}
+ ///
+ /// Sets a continuation onto the .
+ /// The continuation is scheduled to run in the current synchronization context is one exists,
+ /// otherwise in the current task scheduler.
+ ///
+ /// The action to invoke when the has completed.
+ ///
+ /// true to attempt to marshal the continuation back to the original context captured; otherwise, false.
+ ///
+ /// The awaiter was not properly initialized.
+ internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
+ {
+ Debug.Assert(stateMachineBox != null);
+
+ // If the caller wants to continue on the current context/scheduler and there is one,
+ // fall back to using the state machine's delegate.
+ if (continueOnCapturedContext)
+ {
+ SynchronizationContext syncCtx = SynchronizationContext.CurrentNoFlow;
+ if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
+ {
+ Action moveNextAction = stateMachineBox.MoveNextAction;
+ if (!AddTaskContinuation(new SynchronizationContextAwaitTaskContinuation(syncCtx, moveNextAction, flowExecutionContext: false), addBeforeOthers: false))
+ {
+ AwaitTaskContinuation.UnsafeScheduleAction(moveNextAction, this);
+ }
+ return;
+ }
+ else
+ {
+ TaskScheduler scheduler = TaskScheduler.InternalCurrent;
+ if (scheduler != null && scheduler != TaskScheduler.Default)
+ {
+ Action moveNextAction = stateMachineBox.MoveNextAction;
+ if (!AddTaskContinuation(new TaskSchedulerAwaitTaskContinuation(scheduler, moveNextAction, flowExecutionContext: false), addBeforeOthers: false))
+ {
+ AwaitTaskContinuation.UnsafeScheduleAction(moveNextAction, this);
+ }
+ return;
+ }
+ }
+ }
+
+ // Otherwise, add the state machine box directly as the ITaskCompletionAction continuation.
+ // If we're unable to because the task has already completed, queue the delegate.
+ if (!AddTaskContinuation(stateMachineBox, addBeforeOthers: false))
+ {
+ AwaitTaskContinuation.UnsafeScheduleAction(stateMachineBox.MoveNextAction, this);
+ }
+ }
+
/// Creates an awaitable that asynchronously yields back to the current context when awaited.
///
/// A context that, when awaited, will asynchronously transition back into the current context at the