diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
index fc7a7f01a31d..c854f608b35f 100644
--- a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
+++ b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
@@ -10,17 +10,12 @@
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.ExceptionServices;
-using System.Security;
using System.Threading;
using System.Threading.Tasks;
-
#if FEATURE_COMINTEROP
using System.Runtime.InteropServices.WindowsRuntime;
#endif // FEATURE_COMINTEROP
@@ -34,23 +29,17 @@ namespace System.Runtime.CompilerServices
public struct AsyncVoidMethodBuilder
{
/// The synchronization context associated with this operation.
- private SynchronizationContext m_synchronizationContext;
- /// State related to the IAsyncStateMachine.
- private AsyncMethodBuilderCore m_coreState; // mutable struct: must not be readonly
- /// Task used for debugging and logging purposes only. Lazily initialized.
- private Task m_task;
+ private SynchronizationContext _synchronizationContext;
+ /// The builder this void builder wraps.
+ private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly
/// Initializes a new .
/// The initialized .
public static AsyncVoidMethodBuilder Create()
{
- // Capture the current sync context. If there isn't one, use the dummy s_noContextCaptured
- // instance; this allows us to tell the state of no captured context apart from the state
- // of an improperly constructed builder instance.
SynchronizationContext sc = SynchronizationContext.CurrentNoFlow;
- if (sc != null)
- sc.OperationStarted();
- return new AsyncVoidMethodBuilder() { m_synchronizationContext = sc };
+ sc?.OperationStarted();
+ return new AsyncVoidMethodBuilder() { _synchronizationContext = sc };
}
/// Initiates the builder's execution with the associated state machine.
@@ -58,39 +47,15 @@ public static AsyncVoidMethodBuilder Create()
/// The state machine instance, passed by reference.
/// The argument was null (Nothing in Visual Basic).
[DebuggerStepThrough]
- public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
- {
- // See comment on AsyncMethodBuilderCore.Start
- // AsyncMethodBuilderCore.Start(ref stateMachine);
-
- if (stateMachine == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
- Contract.EndContractBlock();
-
- // Run the MoveNext method within a copy-on-write ExecutionContext scope.
- // This allows us to undo any ExecutionContext changes made in MoveNext,
- // so that they won't "leak" out of the first await.
-
- Thread currentThread = Thread.CurrentThread;
- ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
- try
- {
- ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
- stateMachine.MoveNext();
- }
- finally
- {
- ecs.Undo(currentThread);
- }
- }
+ public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+ _builder.Start(ref stateMachine);
/// Associates the builder with the state machine it represents.
/// The heap-allocated state machine object.
/// The argument was null (Nothing in Visual Basic).
/// The builder is incorrectly initialized.
- public void SetStateMachine(IAsyncStateMachine stateMachine)
- {
- m_coreState.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore
- }
+ public void SetStateMachine(IAsyncStateMachine stateMachine) =>
+ _builder.SetStateMachine(stateMachine);
///
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
@@ -102,41 +67,8 @@ public void SetStateMachine(IAsyncStateMachine stateMachine)
public void AwaitOnCompleted(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
- where TStateMachine : IAsyncStateMachine
- {
- try
- {
- AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
- var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
- Debug.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
-
- // If this is our first await, such that we've not yet boxed the state machine, do so now.
- if (m_coreState.m_stateMachine == null)
- {
- if (AsyncCausalityTracer.LoggingOn)
- AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0);
-
- // Box the state machine, then tell the boxed instance to call back into its own builder,
- // so we can cache the boxed reference. NOTE: The language compiler may choose to use
- // a class instead of a struct for the state machine for debugging purposes; in such cases,
- // the stateMachine will already be an object.
- m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null);
- }
-
- awaiter.OnCompleted(continuation);
- }
- catch (Exception exc)
- {
- // Prevent exceptions from leaking to the call site, which could
- // then allow multiple flows of execution through the same async method
- // if the awaiter had already scheduled the continuation by the time
- // the exception was thrown. We propagate the exception on the
- // ThreadPool because we can trust it to not throw, unlike
- // if we were to go to a user-supplied SynchronizationContext,
- // whose Post method could easily throw.
- AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
- }
- }
+ where TStateMachine : IAsyncStateMachine =>
+ _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
///
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
@@ -148,45 +80,23 @@ public void AwaitOnCompleted(
public void AwaitUnsafeOnCompleted(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
- where TStateMachine : IAsyncStateMachine
- {
- try
- {
- AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
- var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
- Debug.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
-
- // If this is our first await, such that we've not yet boxed the state machine, do so now.
- if (m_coreState.m_stateMachine == null)
- {
- if (AsyncCausalityTracer.LoggingOn)
- AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0);
-
- // Box the state machine, then tell the boxed instance to call back into its own builder,
- // so we can cache the boxed reference. NOTE: The language compiler may choose to use
- // a class instead of a struct for the state machine for debugging purposes; in such cases,
- // the stateMachine will already be an object.
- m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null);
- }
-
- awaiter.UnsafeOnCompleted(continuation);
- }
- catch (Exception e)
- {
- AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
- }
- }
+ where TStateMachine : IAsyncStateMachine =>
+ _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
/// Completes the method builder successfully.
public void SetResult()
{
if (AsyncCausalityTracer.LoggingOn)
+ {
AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Completed);
+ }
- if (m_synchronizationContext != null)
+ if (_synchronizationContext != null)
{
NotifySynchronizationContextOfCompletion();
}
+
+ // No need to call _builder.SetResult, as no one pays attention to the task's completion.
}
/// Faults the method builder with an exception.
@@ -195,19 +105,23 @@ public void SetResult()
/// The builder is not initialized.
public void SetException(Exception exception)
{
- if (exception == null) throw new ArgumentNullException(nameof(exception));
- Contract.EndContractBlock();
+ if (exception == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+ }
if (AsyncCausalityTracer.LoggingOn)
+ {
AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Error);
+ }
- if (m_synchronizationContext != null)
+ if (_synchronizationContext != null)
{
// If we captured a synchronization context, Post the throwing of the exception to it
// and decrement its outstanding operation count.
try
{
- AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: m_synchronizationContext);
+ AsyncStateMachineHelpers.ThrowAsync(exception, targetContext: _synchronizationContext);
}
finally
{
@@ -219,28 +133,30 @@ public void SetException(Exception exception)
// Otherwise, queue the exception to be thrown on the ThreadPool. This will
// result in a crash unless legacy exception behavior is enabled by a config
// file or a CLR host.
- AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: null);
+ AsyncStateMachineHelpers.ThrowAsync(exception, targetContext: null);
}
+
+ // No need to call _builder.SetException, as no one pays attention to the task's completion.
}
/// Notifies the current synchronization context that the operation completed.
private void NotifySynchronizationContextOfCompletion()
{
- Debug.Assert(m_synchronizationContext != null, "Must only be used with a non-null context.");
+ Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
try
{
- m_synchronizationContext.OperationCompleted();
+ _synchronizationContext.OperationCompleted();
}
catch (Exception exc)
{
// If the interaction with the SynchronizationContext goes awry,
// fall back to propagating on the ThreadPool.
- AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
+ AsyncStateMachineHelpers.ThrowAsync(exc, targetContext: null);
}
}
/// Lazily instantiate the Task in a non-thread-safe manner.
- private Task Task => m_task ?? (m_task = new Task());
+ private Task Task => _builder.Task;
///
/// Gets an object that may be used to uniquely identify this builder to the debugger.
@@ -249,7 +165,7 @@ private void NotifySynchronizationContextOfCompletion()
/// This property lazily instantiates the ID in a non-thread-safe manner.
/// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner.
///
- private object ObjectIdForDebugger { get { return this.Task; } }
+ private object ObjectIdForDebugger => _builder.Task;
}
///
@@ -267,54 +183,25 @@ public struct AsyncTaskMethodBuilder
private readonly static Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask;
/// The generic builder object to which this non-generic instance delegates.
- private AsyncTaskMethodBuilder m_builder; // mutable struct: must not be readonly
+ private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly
/// Initializes a new .
/// The initialized .
- public static AsyncTaskMethodBuilder Create()
- {
- return default(AsyncTaskMethodBuilder);
- // Note: If ATMB.Create is modified to do any initialization, this
- // method needs to be updated to do m_builder = ATMB.Create().
- }
+ public static AsyncTaskMethodBuilder Create() => default(AsyncTaskMethodBuilder);
/// Initiates the builder's execution with the associated state machine.
/// Specifies the type of the state machine.
/// The state machine instance, passed by reference.
[DebuggerStepThrough]
- public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
- {
- // See comment on AsyncMethodBuilderCore.Start
- // AsyncMethodBuilderCore.Start(ref stateMachine);
-
- if (stateMachine == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
- Contract.EndContractBlock();
-
- // Run the MoveNext method within a copy-on-write ExecutionContext scope.
- // This allows us to undo any ExecutionContext changes made in MoveNext,
- // so that they won't "leak" out of the first await.
-
- Thread currentThread = Thread.CurrentThread;
- ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
- try
- {
- ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
- stateMachine.MoveNext();
- }
- finally
- {
- ecs.Undo(currentThread);
- }
- }
+ public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+ _builder.Start(ref stateMachine);
/// Associates the builder with the state machine it represents.
/// The heap-allocated state machine object.
/// The argument was null (Nothing in Visual Basic).
/// The builder is incorrectly initialized.
- public void SetStateMachine(IAsyncStateMachine stateMachine)
- {
- m_builder.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore
- }
+ public void SetStateMachine(IAsyncStateMachine stateMachine) =>
+ _builder.SetStateMachine(stateMachine);
///
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
@@ -326,10 +213,8 @@ public void SetStateMachine(IAsyncStateMachine stateMachine)
public void AwaitOnCompleted(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
- where TStateMachine : IAsyncStateMachine
- {
- m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
- }
+ where TStateMachine : IAsyncStateMachine =>
+ _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
///
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
@@ -341,10 +226,8 @@ public void AwaitOnCompleted(
public void AwaitUnsafeOnCompleted(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
- where TStateMachine : IAsyncStateMachine
- {
- m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
- }
+ where TStateMachine : IAsyncStateMachine =>
+ _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
/// Gets the for this builder.
/// The representing the builder's asynchronous operation.
@@ -352,7 +235,7 @@ public void AwaitUnsafeOnCompleted(
public Task Task
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get { return m_builder.Task; }
+ get => _builder.Task;
}
///
@@ -361,12 +244,7 @@ public Task Task
///
/// The builder is not initialized.
/// The task has already completed.
- public void SetResult()
- {
- // Accessing AsyncTaskMethodBuilder.s_cachedCompleted is faster than
- // accessing AsyncTaskMethodBuilder.s_defaultResultTask.
- m_builder.SetResult(s_cachedCompleted);
- }
+ public void SetResult() => _builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
///
/// Completes the in the
@@ -376,7 +254,7 @@ public void SetResult()
/// The argument is null (Nothing in Visual Basic).
/// The builder is not initialized.
/// The task has already completed.
- public void SetException(Exception exception) { m_builder.SetException(exception); }
+ public void SetException(Exception exception) => _builder.SetException(exception);
///
/// Called by the debugger to request notification when the first wait operation
@@ -385,10 +263,7 @@ public void SetResult()
///
/// true to enable notification; false to disable a previously set notification.
///
- internal void SetNotificationForWaitCompletion(bool enabled)
- {
- m_builder.SetNotificationForWaitCompletion(enabled);
- }
+ internal void SetNotificationForWaitCompletion(bool enabled) => _builder.SetNotificationForWaitCompletion(enabled);
///
/// Gets an object that may be used to uniquely identify this builder to the debugger.
@@ -398,7 +273,7 @@ internal void SetNotificationForWaitCompletion(bool enabled)
/// It must only be used by the debugger and tracing pruposes, and only in a single-threaded manner
/// when no other threads are in the middle of accessing this property or this.Task.
///
- private object ObjectIdForDebugger { get { return this.Task; } }
+ private object ObjectIdForDebugger => this.Task;
}
///
@@ -415,25 +290,12 @@ public struct AsyncTaskMethodBuilder
/// A cached task for default(TResult).
internal readonly static Task s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
- // WARNING: For performance reasons, the m_task field is lazily initialized.
- // For correct results, the struct AsyncTaskMethodBuilder must
- // always be used from the same location/copy, at least until m_task is
- // initialized. If that guarantee is broken, the field could end up being
- // initialized on the wrong copy.
-
- /// State related to the IAsyncStateMachine.
- private AsyncMethodBuilderCore m_coreState; // mutable struct: must not be readonly
/// The lazily-initialized built task.
- private Task m_task; // lazily-initialized: must not be readonly
+ private Task _taskAndStateMachine; // lazily-initialized: must not be readonly
/// Initializes a new .
/// The initialized .
- public static AsyncTaskMethodBuilder Create()
- {
- return default(AsyncTaskMethodBuilder);
- // NOTE: If this method is ever updated to perform more initialization,
- // ATMB.Create must also be updated to call this Create method.
- }
+ public static AsyncTaskMethodBuilder Create() => default(AsyncTaskMethodBuilder);
/// Initiates the builder's execution with the associated state machine.
/// Specifies the type of the state machine.
@@ -441,11 +303,10 @@ public static AsyncTaskMethodBuilder Create()
[DebuggerStepThrough]
public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
- // See comment on AsyncMethodBuilderCore.Start
- // AsyncMethodBuilderCore.Start(ref stateMachine);
-
- if (stateMachine == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
- Contract.EndContractBlock();
+ if (stateMachine == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
+ }
// Run the MoveNext method within a copy-on-write ExecutionContext scope.
// This allows us to undo any ExecutionContext changes made in MoveNext,
@@ -470,7 +331,9 @@ public void Start(ref TStateMachine stateMachine) where TStateMac
/// The builder is incorrectly initialized.
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
- m_coreState.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore
+ if (stateMachine == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
+ if (_taskAndStateMachine != null) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
+ _taskAndStateMachine = stateMachine as Task;
}
///
@@ -487,29 +350,11 @@ public void AwaitOnCompleted(
{
try
{
- AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
- var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
- Debug.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
-
- // If this is our first await, such that we've not yet boxed the state machine, do so now.
- if (m_coreState.m_stateMachine == null)
- {
- // Force the Task to be initialized prior to the first suspending await so
- // that the original stack-based builder has a reference to the right Task.
- Task builtTask = this.Task;
-
- // Box the state machine, then tell the boxed instance to call back into its own builder,
- // so we can cache the boxed reference. NOTE: The language compiler may choose to use
- // a class instead of a struct for the state machine for debugging purposes; in such cases,
- // the stateMachine will already be an object.
- m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, builtTask);
- }
-
- awaiter.OnCompleted(continuation);
+ awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).Delegate);
}
catch (Exception e)
{
- AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
+ AsyncStateMachineHelpers.ThrowAsync(e, targetContext: null);
}
}
@@ -527,30 +372,56 @@ public void AwaitUnsafeOnCompleted(
{
try
{
- AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
- var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
- Debug.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
+ awaiter.UnsafeOnCompleted(GetStateMachineBox(ref stateMachine).Delegate);
+ }
+ catch (Exception e)
+ {
+ AsyncStateMachineHelpers.ThrowAsync(e, targetContext: null);
+ }
+ }
- // If this is our first await, such that we've not yet boxed the state machine, do so now.
- if (m_coreState.m_stateMachine == null)
+ /// Gets the "boxed" state machine object.
+ /// Specifies the type of the async state machine.
+ /// The state machine.
+ /// The "boxed" state machine.
+ private AsyncStateMachineBox GetStateMachineBox(
+ ref TStateMachine stateMachine)
+ where TStateMachine : IAsyncStateMachine
+ {
+ ExecutionContext currentContext = ExecutionContext.Capture();
+
+ // If we don't have a reference to a state machine already, this is the first time
+ // we're awaiting something, and we need to create a new state machine object and associated
+ // delegate. If we already have one, we just use and update the existing objects.
+ var box = _taskAndStateMachine as AsyncStateMachineBox;
+ if (box == null)
+ {
+ // Alert a listening debugger that we can't make forward progress unless it slips threads.
+ // If we don't do this, and a method that uses "await foo;" is invoked through funceval,
+ // we could end up hooking up a callback to push forward the async method's state machine,
+ // the debugger would then abort the funceval after it takes too long, and then continuing
+ // execution could result in another callback being hooked up. At that point we have
+ // multiple callbacks registered to push the state machine, which could result in bad behavior.
+ Debugger.NotifyOfCrossThreadDependency();
+
+ // Create the box and store the state machine into it.
+ _taskAndStateMachine = box = new AsyncStateMachineBox();
+ box.StateMachine = stateMachine;
+ box.Context = currentContext;
+ box.Delegate = box.MoveNext;
+ if (AsyncCausalityTracer.LoggingOn)
{
- // Force the Task to be initialized prior to the first suspending await so
- // that the original stack-based builder has a reference to the right Task.
- Task builtTask = this.Task;
-
- // Box the state machine, then tell the boxed instance to call back into its own builder,
- // so we can cache the boxed reference. NOTE: The language compiler may choose to use
- // a class instead of a struct for the state machine for debugging purposes; in such cases,
- // the stateMachine will already be an object.
- m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, builtTask);
+ box.Delegate = AsyncStateMachineHelpers.OutputAsyncCausalityEvents(box, box.Delegate);
}
-
- awaiter.UnsafeOnCompleted(continuation);
}
- catch (Exception e)
+ else if (box.Context != currentContext)
{
- AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
+ // This is not the first await. If the context has changed since we last saw it,
+ // update it.
+ box.Context = currentContext;
}
+
+ return box;
}
/// Gets the for this builder.
@@ -558,12 +429,12 @@ public void AwaitUnsafeOnCompleted(
public Task Task
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get { return m_task ?? InitializeTask(); }
+ get => _taskAndStateMachine ?? InitializeTask();
}
/// Initializes the task, which must not yet be initialized.
[MethodImpl(MethodImplOptions.NoInlining)]
- private Task InitializeTask() => (m_task = new Task());
+ private Task InitializeTask() => (_taskAndStateMachine = new Task());
///
/// Completes the in the
@@ -575,10 +446,10 @@ public void SetResult(TResult result)
{
// Get the currently stored task, which will be non-null if get_Task has already been accessed.
// If there isn't one, get a task and store it.
- if (m_task == null)
+ if (_taskAndStateMachine == null)
{
- m_task = GetTaskForResult(result);
- Debug.Assert(m_task != null, "GetTaskForResult should never return null");
+ _taskAndStateMachine = GetTaskForResult(result);
+ Debug.Assert(_taskAndStateMachine != null, $"{nameof(GetTaskForResult)} should never return null");
}
else
{
@@ -591,14 +462,14 @@ public void SetResult(TResult result)
/// The result to use to complete the task.
private void SetExistingTaskResult(TResult result)
{
- Debug.Assert(m_task != null, "Expected non-null task");
+ Debug.Assert(_taskAndStateMachine != null, "Expected non-null task");
if (AsyncCausalityTracer.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
{
LogExistingTaskCompletion();
}
- if (!m_task.TrySetResult(result))
+ if (!_taskAndStateMachine.TrySetResult(result))
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
}
@@ -607,17 +478,17 @@ private void SetExistingTaskResult(TResult result)
/// Handles logging for the successful completion of an operation.
private void LogExistingTaskCompletion()
{
- Debug.Assert(m_task != null);
+ Debug.Assert(_taskAndStateMachine != null);
if (AsyncCausalityTracer.LoggingOn)
{
- AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, m_task.Id, AsyncCausalityStatus.Completed);
+ AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, _taskAndStateMachine.Id, AsyncCausalityStatus.Completed);
}
// only log if we have a real task that was previously created
if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
{
- System.Threading.Tasks.Task.RemoveFromActiveTasks(m_task.Id);
+ System.Threading.Tasks.Task.RemoveFromActiveTasks(_taskAndStateMachine.Id);
}
}
@@ -630,13 +501,13 @@ private void LogExistingTaskCompletion()
internal void SetResult(Task completedTask)
{
Contract.Requires(completedTask != null, "Expected non-null task");
- Contract.Requires(completedTask.Status == TaskStatus.RanToCompletion, "Expected a successfully completed task");
+ Contract.Requires(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task");
// Get the currently stored task, which will be non-null if get_Task has already been accessed.
// If there isn't one, store the supplied completed task.
- if (m_task == null)
+ if (_taskAndStateMachine == null)
{
- m_task = completedTask;
+ _taskAndStateMachine = completedTask;
}
else
{
@@ -654,11 +525,12 @@ internal void SetResult(Task completedTask)
/// The task has already completed.
public void SetException(Exception exception)
{
- if (exception == null) throw new ArgumentNullException(nameof(exception));
- Contract.EndContractBlock();
-
+ if (exception == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+ }
- var task = m_task;
+ Task task = _taskAndStateMachine;
if (task == null)
{
// Get the task, forcing initialization if it hasn't already been initialized.
@@ -671,7 +543,7 @@ public void SetException(Exception exception)
task.TrySetCanceled(oce.CancellationToken, oce) :
task.TrySetException(exception);
- // Unlike with TaskCompletionSource, we do not need to spin here until m_task is completed,
+ // Unlike with TaskCompletionSource, we do not need to spin here until _taskAndStateMachine is completed,
// since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code
// that depends on the task having completely completed. Moreover, with correct usage,
// SetResult or SetException should only be called once, so the Try* methods should always
@@ -709,7 +581,7 @@ internal void SetNotificationForWaitCompletion(bool enabled)
/// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
/// when no other threads are in the middle of accessing this property or this.Task.
///
- private object ObjectIdForDebugger { get { return this.Task; } }
+ private object ObjectIdForDebugger => this.Task;
///
/// Gets a task for the specified result. This will either
@@ -836,112 +708,62 @@ private static Task[] CreateInt32Tasks()
/// Specifies the result type.
/// The result for the task.
/// The cacheable task.
- internal static Task CreateCacheableTask(TResult result)
- {
- return new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
- }
+ internal static Task CreateCacheableTask(TResult result) =>
+ new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
}
- /// Holds state related to the builder's IAsyncStateMachine.
- /// This is a mutable struct. Be very delicate with it.
- internal struct AsyncMethodBuilderCore
+ /// 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.
+ internal sealed class AsyncStateMachineBox :
+ Task, IAsyncStateMachine, IDebuggingAsyncStateMachineAccessor
+ where TStateMachine : IAsyncStateMachine
{
- /// A reference to the heap-allocated state machine object associated with this builder.
- internal IAsyncStateMachine m_stateMachine;
- /// A cached Action delegate used when dealing with a default ExecutionContext.
- internal Action m_defaultContextAction;
+ /// 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();
- // This method is copy&pasted into the public Start methods to avoid size overhead of valuetype generic instantiations.
- // Ideally, we would build intrinsics to get the raw ref address and raw code address of MoveNext, and just use the shared implementation.
+ /// 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;
+ /// A delegate to the method.
+ public Action Delegate;
- /// Associates the builder with the state machine it represents.
- /// The heap-allocated state machine object.
- /// The argument was null (Nothing in Visual Basic).
- /// The builder is incorrectly initialized.
- public void SetStateMachine(IAsyncStateMachine stateMachine)
+ /// Call MoveNext on .
+ public void MoveNext()
{
- if (stateMachine == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
- Contract.EndContractBlock();
- if (m_stateMachine != null) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
- m_stateMachine = stateMachine;
- }
-
- ///
- /// Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method.
- /// On first invocation, the supplied state machine will be boxed.
- ///
- /// Specifies the type of the method builder used.
- /// Specifies the type of the state machine used.
- /// The builder.
- /// The state machine.
- /// An Action to provide to the awaiter.
- internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize)
- {
- Debug.Assert(m_defaultContextAction == null || m_stateMachine != null,
- "Expected non-null m_stateMachine on non-null m_defaultContextAction");
-
- // Alert a listening debugger that we can't make forward progress unless it slips threads.
- // If we don't do this, and a method that uses "await foo;" is invoked through funceval,
- // we could end up hooking up a callback to push forward the async method's state machine,
- // the debugger would then abort the funceval after it takes too long, and then continuing
- // execution could result in another callback being hooked up. At that point we have
- // multiple callbacks registered to push the state machine, which could result in bad behavior.
- Debugger.NotifyOfCrossThreadDependency();
-
- // The builder needs to flow ExecutionContext, so capture it.
- var capturedContext = ExecutionContext.Capture();
-
- // If the ExecutionContext is the default context, try to use a cached delegate, creating one if necessary.
- Action action;
- MoveNextRunner runner;
- if (capturedContext == ExecutionContext.Default)
+ if (Context == null)
{
- // Get the cached delegate, and if it's non-null, return it.
- action = m_defaultContextAction;
- if (action != null)
- {
- Debug.Assert(m_stateMachine != null, "If the delegate was set, the state machine should have been as well.");
- return action;
- }
-
- // There wasn't a cached delegate, so create one and cache it.
- // The delegate won't be usable until we set the MoveNextRunner's target state machine.
- runner = new MoveNextRunner(m_stateMachine);
-
- action = new Action(runner.RunWithDefaultContext);
- if (taskForTracing != null)
- {
- action = OutputAsyncCausalityEvents(taskForTracing, action);
- }
- m_defaultContextAction = action;
+ StateMachine.MoveNext();
}
- // Otherwise, create an Action that flows this context. The context may be null.
- // The delegate won't be usable until we set the MoveNextRunner's target state machine.
else
{
- var runnerWithContext = new MoveNextRunnerWithContext(capturedContext, m_stateMachine);
- runner = runnerWithContext;
- action = new Action(runnerWithContext.RunWithCapturedContext);
-
- if (taskForTracing != null)
- {
- action = OutputAsyncCausalityEvents(taskForTracing, action);
- }
-
- // NOTE: If capturedContext is null, we could create the Action to point directly
- // to m_stateMachine.MoveNext. However, that follows a much more expensive
- // delegate creation path.
+ ExecutionContext.Run(Context, s_callback, this);
}
+ }
- if (m_stateMachine == null)
- runnerToInitialize = runner;
+ /// Store the supplied state machine.
+ public void SetStateMachine(IAsyncStateMachine stateMachine) => StateMachine.SetStateMachine(stateMachine);
- return action;
- }
+ /// Gets the state machine as a boxed object. This should only be used for debugging purposes.
+ IAsyncStateMachine IDebuggingAsyncStateMachineAccessor.GetStateMachineObject() => StateMachine; // boxes, only use for debugging
+ }
- private Action OutputAsyncCausalityEvents(Task innerTask, Action continuation)
- {
- return CreateContinuationWrapper(continuation, () =>
+ ///
+ /// An interface implemented by to allow access
+ /// to its StateMachine without knowing the generic types associated with a given instance.
+ ///
+ interface IDebuggingAsyncStateMachineAccessor
+ {
+ /// Gets the state machine as a boxed object. This should only be used for debugging purposes.
+ IAsyncStateMachine GetStateMachineObject();
+ }
+
+ /// Shared helpers for manipulating state related to async state machines.
+ internal static class AsyncStateMachineHelpers
+ {
+ internal static Action OutputAsyncCausalityEvents(Task innerTask, Action continuation) =>
+ CreateContinuationWrapper(continuation, () =>
{
AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, innerTask.Id, CausalitySynchronousWork.Execution);
@@ -950,30 +772,64 @@ private Action OutputAsyncCausalityEvents(Task innerTask, Action continuation)
AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution);
}, innerTask);
- }
- internal void PostBoxInitialization(IAsyncStateMachine stateMachine, MoveNextRunner runner, Task builtTask)
+ internal static Action CreateContinuationWrapper(Action continuation, Action invokeAction, Task innerTask = null) =>
+ new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
+
+ internal static Action TryGetStateMachineForDebugger(Action action)
{
- if (builtTask != null)
+ object target = action.Target;
+
+ var sm = target as IDebuggingAsyncStateMachineAccessor;
+ if (sm != null)
{
- if (AsyncCausalityTracer.LoggingOn)
- AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, builtTask.Id, "Async: " + stateMachine.GetType().Name, 0);
+ return sm.GetStateMachineObject().MoveNext;
+ }
- if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
- System.Threading.Tasks.Task.AddToActiveTasks(builtTask);
+ var continuationWrapper = target as ContinuationWrapper;
+ if (continuationWrapper != null)
+ {
+ return TryGetStateMachineForDebugger(continuationWrapper._continuation);
}
- m_stateMachine = stateMachine;
- m_stateMachine.SetStateMachine(m_stateMachine);
+ return action;
+ }
- Debug.Assert(runner.m_stateMachine == null, "The runner's state machine should not yet have been populated.");
- Debug.Assert(m_stateMachine != null, "The builder's state machine field should have been initialized.");
+ ///
+ /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
+ /// However debuggers and profilers need more information about what that action is. (In particular what
+ /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
+ /// which when invoked just does the original action (the invoke action), but also remembers other information
+ /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
+ // We also store that task if the action is associate with at task.
+ ///
+ private class ContinuationWrapper
+ {
+ internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
+ private readonly Action _invokeAction; // This wrapper is an action that wraps another action, this is that Action.
+ internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
- // Now that we have the state machine, store it into the runner that the action delegate points to.
- // And return the action.
- runner.m_stateMachine = m_stateMachine; // only after this line is the Action delegate usable
+ internal ContinuationWrapper(Action continuation, Action invokeAction, Task innerTask)
+ {
+ Contract.Requires(continuation != null, "Expected non-null continuation");
+
+ // If we don't have a task, see if our continuation is a wrapper and use that.
+ if (innerTask == null)
+ {
+ innerTask = GetContinuationTask(continuation);
+ }
+
+ _continuation = continuation;
+ _innerTask = innerTask;
+ _invokeAction = invokeAction;
+ }
+
+ internal void Invoke() => _invokeAction();
}
+ internal static Task GetContinuationTask(Action continuation) =>
+ (continuation?.Target as ContinuationWrapper)?._innerTask;
+
/// Throws the exception on the ThreadPool.
/// The exception to propagate.
/// The target context on which to propagate the exception. Null to use the ThreadPool.
@@ -1008,128 +864,5 @@ internal static void ThrowAsync(Exception exception, SynchronizationContext targ
ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi);
}
}
-
- /// Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext.
- internal sealed class MoveNextRunnerWithContext : MoveNextRunner
- {
- /// The context with which to run MoveNext.
- private readonly ExecutionContext m_context;
-
- /// Initializes the runner.
- /// The context with which to run MoveNext.
- internal MoveNextRunnerWithContext(ExecutionContext context, IAsyncStateMachine stateMachine) : base(stateMachine)
- {
- m_context = context;
- }
-
- /// Invokes MoveNext under the provided context.
- internal void RunWithCapturedContext()
- {
- Debug.Assert(m_stateMachine != null, "The state machine must have been set before calling Run.");
-
- if (m_context != null)
- {
- // Use the context and callback to invoke m_stateMachine.MoveNext.
- ExecutionContext.Run(m_context, InvokeMoveNextCallback, m_stateMachine);
- }
- else
- {
- m_stateMachine.MoveNext();
- }
- }
- }
-
- /// Provides the ability to invoke a state machine's MoveNext method.
- internal class MoveNextRunner
- {
- /// The state machine whose MoveNext method should be invoked.
- internal IAsyncStateMachine m_stateMachine;
-
- /// Initializes the runner.
- internal MoveNextRunner(IAsyncStateMachine stateMachine)
- {
- m_stateMachine = stateMachine;
- }
-
- /// Invokes MoveNext under the default context.
- internal void RunWithDefaultContext()
- {
- Debug.Assert(m_stateMachine != null, "The state machine must have been set before calling Run.");
- ExecutionContext.Run(ExecutionContext.Default, InvokeMoveNextCallback, m_stateMachine);
- }
-
- /// Gets a delegate to the InvokeMoveNext method.
- protected static readonly ContextCallback InvokeMoveNextCallback = sm => ((IAsyncStateMachine)sm).MoveNext();
- }
-
- ///
- /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
- /// However debuggers and profilers need more information about what that action is. (In particular what
- /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
- /// which when invoked just does the original action (the invoke action), but also remembers other information
- /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
- // We also store that task if the action is associate with at task.
- ///
- private class ContinuationWrapper
- {
- internal readonly Action m_continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
- private readonly Action m_invokeAction; // This wrapper is an action that wraps another action, this is that Action.
- internal readonly Task m_innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
-
- internal ContinuationWrapper(Action continuation, Action invokeAction, Task innerTask)
- {
- Contract.Requires(continuation != null, "Expected non-null continuation");
-
- // If we don't have a task, see if our continuation is a wrapper and use that.
- if (innerTask == null)
- innerTask = TryGetContinuationTask(continuation);
-
- m_continuation = continuation;
- m_innerTask = innerTask;
- m_invokeAction = invokeAction;
- }
-
- internal void Invoke()
- {
- m_invokeAction();
- }
- }
-
- internal static Action CreateContinuationWrapper(Action continuation, Action invokeAction, Task innerTask = null)
- {
- return new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
- }
-
- internal static Action TryGetStateMachineForDebugger(Action action)
- {
- object target = action.Target;
- var runner = target as AsyncMethodBuilderCore.MoveNextRunner;
- if (runner != null)
- {
- return new Action(runner.m_stateMachine.MoveNext);
- }
-
- var continuationWrapper = target as ContinuationWrapper;
- if (continuationWrapper != null)
- {
- return TryGetStateMachineForDebugger(continuationWrapper.m_continuation);
- }
-
- return action;
- }
-
- ///
- /// Given an action, see if it is a contiunation wrapper and has a Task associated with it. If so return it (null otherwise)
- ///
- internal static Task TryGetContinuationTask(Action action)
- {
- if (action != null)
- {
- var asWrapper = action.Target as ContinuationWrapper;
- if (asWrapper != null)
- return asWrapper.m_innerTask;
- }
- return null;
- }
}
}
diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs b/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs
index c35658e54cdc..271d9343aa6f 100644
--- a/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs
+++ b/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs
@@ -236,7 +236,7 @@ private static Action OutputWaitEtwEvents(Task task, Action continuation)
var currentTaskAtBegin = Task.InternalCurrent;
// If this task's continuation is another task, get it.
- var continuationTask = AsyncMethodBuilderCore.TryGetContinuationTask(continuation);
+ Task continuationTask = AsyncStateMachineHelpers.GetContinuationTask(continuation);
etwLog.TaskWaitBegin(
(currentTaskAtBegin != null ? currentTaskAtBegin.m_taskScheduler.Id : TaskScheduler.Default.Id),
(currentTaskAtBegin != null ? currentTaskAtBegin.Id : 0),
@@ -249,7 +249,7 @@ private static Action OutputWaitEtwEvents(Task task, Action continuation)
// is enabled, and in doing so it allows us to pass the awaited task's information into the end event
// in a purely pay-for-play manner (the alternatively would be to increase the size of TaskAwaiter
// just for this ETW purpose, not pay-for-play, since GetResult would need to know whether a real yield occurred).
- return AsyncMethodBuilderCore.CreateContinuationWrapper(continuation, () =>
+ return AsyncStateMachineHelpers.CreateContinuationWrapper(continuation, () =>
{
if (Task.s_asyncDebuggingEnabled)
{
diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/YieldAwaitable.cs b/src/mscorlib/src/System/Runtime/CompilerServices/YieldAwaitable.cs
index f1c777252632..d1a664e02232 100644
--- a/src/mscorlib/src/System/Runtime/CompilerServices/YieldAwaitable.cs
+++ b/src/mscorlib/src/System/Runtime/CompilerServices/YieldAwaitable.cs
@@ -124,7 +124,7 @@ private static Action OutputCorrelationEtwEvent(Action continuation)
// fire the correlation ETW event
TplEtwProvider.Log.AwaitTaskContinuationScheduled(TaskScheduler.Current.Id, (currentTask != null) ? currentTask.Id : 0, continuationId);
- return AsyncMethodBuilderCore.CreateContinuationWrapper(continuation, () =>
+ return AsyncStateMachineHelpers.CreateContinuationWrapper(continuation, () =>
{
var etwLog = TplEtwProvider.Log;
etwLog.TaskWaitContinuationStarted(continuationId);
diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs
index c19fd3ec64b5..e435077ee6bc 100644
--- a/src/mscorlib/src/System/Threading/Tasks/Task.cs
+++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs
@@ -6130,7 +6130,7 @@ internal static Delegate[] GetDelegatesFromContinuationObject(object continuatio
Action singleAction = continuationObject as Action;
if (singleAction != null)
{
- return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(singleAction) };
+ return new Delegate[] { AsyncStateMachineHelpers.TryGetStateMachineForDebugger(singleAction) };
}
TaskContinuation taskContinuation = continuationObject as TaskContinuation;
diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs
index de222352c9d9..8ec7d663e4aa 100644
--- a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs
+++ b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs
@@ -812,7 +812,7 @@ protected static void ThrowAsyncIfNecessary(Exception exc)
internal override Delegate[] GetDelegateContinuationsForDebugger()
{
Debug.Assert(m_action != null);
- return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(m_action) };
+ return new Delegate[] { AsyncStateMachineHelpers.TryGetStateMachineForDebugger(m_action) };
}
}
}