summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilip Navara <filip.navara@gmail.com>2019-01-16 19:26:14 +0100
committerJan Kotas <jkotas@microsoft.com>2019-01-17 04:16:15 -0800
commit31cbeb815c0edc4781c58c8dd094008df2f9c4ef (patch)
tree1c05c2e4207b3fac25c0fdb4ec9d3c4d6841eb91
parent80720a220c340e99aa88d04ebae8118eb4fc198b (diff)
downloadcoreclr-31cbeb815c0edc4781c58c8dd094008df2f9c4ef.tar.gz
coreclr-31cbeb815c0edc4781c58c8dd094008df2f9c4ef.tar.bz2
coreclr-31cbeb815c0edc4781c58c8dd094008df2f9c4ef.zip
Move AsyncMethodBuilder to shared. (dotnet/corert#6832)
* Move AsyncMethodBuilder to shared. * Bring back PROJECTN. * #if-out TrackAsyncMethodCompletion for CoreRT. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
-rw-r--r--src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems1
-rw-r--r--src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs1
-rw-r--r--src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs1138
3 files changed, 1140 insertions, 0 deletions
diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
index d28a53ff17..b7369c1ddc 100644
--- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
+++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
@@ -496,6 +496,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AccessedThroughPropertyAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncIteratorMethodBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncIteratorStateMachineAttribute.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncMethodBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncMethodBuilderAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncStateMachineAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\AsyncValueTaskMethodBuilder.cs" />
diff --git a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs
index 16874d8040..fde28527b4 100644
--- a/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs
+++ b/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/ActivityTracker.cs
@@ -5,6 +5,7 @@
using System;
using System.Diagnostics;
using System.Threading;
+using System.Runtime.CompilerServices;
#if !ES_BUILD_AGAINST_DOTNET_V35
using Contract = System.Diagnostics.Contracts.Contract;
#else
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
new file mode 100644
index 0000000000..4277c00464
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
@@ -0,0 +1,1138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+//
+//
+//
+// Compiler-targeted types that build tasks for use as the return types of asynchronous methods.
+//
+// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.Reflection;
+using System.Runtime.ExceptionServices;
+#if FEATURE_COMINTEROP
+using System.Runtime.InteropServices.WindowsRuntime;
+#endif // FEATURE_COMINTEROP
+using System.Threading;
+using System.Threading.Tasks;
+using System.Text;
+using Internal.Runtime.CompilerServices;
+using Internal.Runtime.Augments;
+using Internal.Threading.Tasks;
+
+namespace System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Provides a builder for asynchronous methods that return void.
+ /// This type is intended for compiler use only.
+ /// </summary>
+ public struct AsyncVoidMethodBuilder
+ {
+ /// <summary>The synchronization context associated with this operation.</summary>
+ private SynchronizationContext _synchronizationContext;
+ /// <summary>The builder this void builder wraps.</summary>
+ private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly
+
+ /// <summary>Initializes a new <see cref="AsyncVoidMethodBuilder"/>.</summary>
+ /// <returns>The initialized <see cref="AsyncVoidMethodBuilder"/>.</returns>
+ public static AsyncVoidMethodBuilder Create()
+ {
+ SynchronizationContext sc = SynchronizationContext.Current;
+ sc?.OperationStarted();
+ return new AsyncVoidMethodBuilder() { _synchronizationContext = sc };
+ }
+
+ /// <summary>Initiates the builder's execution with the associated state machine.</summary>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
+ [DebuggerStepThrough]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+ AsyncMethodBuilderCore.Start(ref stateMachine);
+
+ /// <summary>Associates the builder with the state machine it represents.</summary>
+ /// <param name="stateMachine">The heap-allocated state machine object.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
+ public void SetStateMachine(IAsyncStateMachine stateMachine) =>
+ _builder.SetStateMachine(stateMachine);
+
+ /// <summary>
+ /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
+ /// </summary>
+ /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitOnCompleted<TAwaiter, TStateMachine>(
+ ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : INotifyCompletion
+ where TStateMachine : IAsyncStateMachine =>
+ _builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
+
+ /// <summary>
+ /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
+ /// </summary>
+ /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
+ ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : ICriticalNotifyCompletion
+ where TStateMachine : IAsyncStateMachine =>
+ _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
+
+ /// <summary>Completes the method builder successfully.</summary>
+ public void SetResult()
+ {
+ if (AsyncCausalitySupport.LoggingOn)
+ {
+ AsyncCausalitySupport.TraceOperationCompletedSuccess(this.Task);
+ }
+
+ // Mark the builder as completed. As this is a void-returning method, this mostly
+ // doesn't matter, but it can affect things like debug events related to finalization.
+ _builder.SetResult();
+
+ if (_synchronizationContext != null)
+ {
+ NotifySynchronizationContextOfCompletion();
+ }
+ }
+
+ /// <summary>Faults the method builder with an exception.</summary>
+ /// <param name="exception">The exception that is the cause of this fault.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
+ public void SetException(Exception exception)
+ {
+ if (exception == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+ }
+
+ if (AsyncCausalitySupport.LoggingOn)
+ {
+ AsyncCausalitySupport.TraceOperationCompletedError(this.Task);
+ }
+
+ 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: _synchronizationContext);
+ }
+ finally
+ {
+ NotifySynchronizationContextOfCompletion();
+ }
+ }
+ else
+ {
+ // 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);
+ }
+
+ // The exception was propagated already; we don't need or want to fault the builder, just mark it as completed.
+ _builder.SetResult();
+ }
+
+ /// <summary>Notifies the current synchronization context that the operation completed.</summary>
+ private void NotifySynchronizationContextOfCompletion()
+ {
+ Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
+ try
+ {
+ _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);
+ }
+ }
+
+ /// <summary>Lazily instantiate the Task in a non-thread-safe manner.</summary>
+ private Task Task => _builder.Task;
+
+ /// <summary>
+ /// Gets an object that may be used to uniquely identify this builder to the debugger.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger;
+ }
+
+ /// <summary>
+ /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>.
+ /// This type is intended for compiler use only.
+ /// </summary>
+ /// <remarks>
+ /// AsyncTaskMethodBuilder is a value type, and thus it is copied by value.
+ /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
+ /// or else the copies may end up building distinct Task instances.
+ /// </remarks>
+ public struct AsyncTaskMethodBuilder
+ {
+ /// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
+#if PROJECTN
+ private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskCache.CreateCacheableTask<VoidTaskResult>(default(VoidTaskResult));
+#else
+ private readonly static Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;
+#endif
+
+ /// <summary>The generic builder object to which this non-generic instance delegates.</summary>
+ private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field.
+
+ /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
+ /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
+ public static AsyncTaskMethodBuilder Create() => default;
+
+ /// <summary>Initiates the builder's execution with the associated state machine.</summary>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+ [DebuggerStepThrough]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+ AsyncMethodBuilderCore.Start(ref stateMachine);
+
+ /// <summary>Associates the builder with the state machine it represents.</summary>
+ /// <param name="stateMachine">The heap-allocated state machine object.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
+ public void SetStateMachine(IAsyncStateMachine stateMachine) =>
+ m_builder.SetStateMachine(stateMachine);
+
+ /// <summary>
+ /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
+ /// </summary>
+ /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitOnCompleted<TAwaiter, TStateMachine>(
+ ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : INotifyCompletion
+ where TStateMachine : IAsyncStateMachine =>
+ m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
+
+ /// <summary>
+ /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
+ /// </summary>
+ /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
+ ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : ICriticalNotifyCompletion
+ where TStateMachine : IAsyncStateMachine =>
+ m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
+
+ /// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary>
+ /// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns>
+ /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
+ public Task Task
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => m_builder.Task;
+ }
+
+ /// <summary>
+ /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
+ /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state.
+ /// </summary>
+ /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
+ /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
+ public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
+
+ /// <summary>
+ /// Completes the <see cref="System.Threading.Tasks.Task"/> in the
+ /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
+ /// </summary>
+ /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
+ /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
+ public void SetException(Exception exception) => m_builder.SetException(exception);
+
+ /// <summary>
+ /// Called by the debugger to request notification when the first wait operation
+ /// (await, Wait, Result, etc.) on this builder's task completes.
+ /// </summary>
+ /// <param name="enabled">
+ /// true to enable notification; false to disable a previously set notification.
+ /// </param>
+ internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
+
+ /// <summary>
+ /// Gets an object that may be used to uniquely identify this builder to the debugger.
+ /// </summary>
+ /// <remarks>
+ /// This property lazily instantiates the ID in a non-thread-safe manner.
+ /// 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.
+ /// </remarks>
+ internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
+ }
+
+ /// <summary>
+ /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task{TResult}"/>.
+ /// This type is intended for compiler use only.
+ /// </summary>
+ /// <remarks>
+ /// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value.
+ /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
+ /// or else the copies may end up building distinct Task instances.
+ /// </remarks>
+ public struct AsyncTaskMethodBuilder<TResult>
+ {
+#if !PROJECTN
+ /// <summary>A cached task for default(TResult).</summary>
+ internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
+#endif
+
+ /// <summary>The lazily-initialized built task.</summary>
+ private Task<TResult> m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field.
+
+ /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
+ /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
+ public static AsyncTaskMethodBuilder<TResult> Create()
+ {
+ return default;
+ // NOTE: If this method is ever updated to perform more initialization,
+ // other Create methods like AsyncTaskMethodBuilder.Create and
+ // AsyncValueTaskMethodBuilder.Create must be updated to call this.
+ }
+
+ /// <summary>Initiates the builder's execution with the associated state machine.</summary>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+ [DebuggerStepThrough]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+ AsyncMethodBuilderCore.Start(ref stateMachine);
+
+ /// <summary>Associates the builder with the state machine it represents.</summary>
+ /// <param name="stateMachine">The heap-allocated state machine object.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
+ public void SetStateMachine(IAsyncStateMachine stateMachine)
+ {
+ if (stateMachine == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
+ }
+
+ if (m_task != null)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
+ }
+
+ // SetStateMachine was originally needed in order to store the boxed state machine reference into
+ // the boxed copy. Now that a normal box is no longer used, SetStateMachine is also legacy. We need not
+ // do anything here, and thus assert to ensure we're not calling this from our own implementations.
+ Debug.Fail("SetStateMachine should not be used.");
+ }
+
+ /// <summary>
+ /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
+ /// </summary>
+ /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitOnCompleted<TAwaiter, TStateMachine>(
+ ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : INotifyCompletion
+ where TStateMachine : IAsyncStateMachine
+ {
+ try
+ {
+ awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
+ }
+ catch (Exception e)
+ {
+ AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
+ }
+ }
+
+ /// <summary>
+ /// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
+ /// </summary>
+ /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="awaiter">The awaiter.</param>
+ /// <param name="stateMachine">The state machine.</param>
+ public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
+ ref TAwaiter awaiter, ref TStateMachine stateMachine)
+ where TAwaiter : ICriticalNotifyCompletion
+ where TStateMachine : IAsyncStateMachine
+ {
+ IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
+
+ // The null tests here ensure that the jit can optimize away the interface
+ // tests when TAwaiter is a ref type.
+
+ if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter))
+ {
+ ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
+ TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
+ }
+ else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter))
+ {
+ ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
+ TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
+ }
+ else if ((null != (object)default(TAwaiter)) && (awaiter is IStateMachineBoxAwareAwaiter))
+ {
+ try
+ {
+ ((IStateMachineBoxAwareAwaiter)awaiter).AwaitUnsafeOnCompleted(box);
+ }
+ catch (Exception e)
+ {
+ // Whereas with Task the code that hooks up and invokes the continuation is all local to corelib,
+ // with ValueTaskAwaiter we may be calling out to an arbitrary implementation of IValueTaskSource
+ // wrapped in the ValueTask, and as such we protect against errant exceptions that may emerge.
+ // We don't want such exceptions propagating back into the async method, which can't handle
+ // exceptions well at that location in the state machine, especially if the exception may occur
+ // after the ValueTaskAwaiter already successfully hooked up the callback, in which case it's possible
+ // two different flows of execution could end up happening in the same async method call.
+ AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
+ }
+ }
+ else
+ {
+ // The awaiter isn't specially known. Fall back to doing a normal await.
+ try
+ {
+ awaiter.UnsafeOnCompleted(box.MoveNextAction);
+ }
+ catch (Exception e)
+ {
+ AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
+ }
+ }
+ }
+
+ /// <summary>Gets the "boxed" state machine object.</summary>
+ /// <typeparam name="TStateMachine">Specifies the type of the async state machine.</typeparam>
+ /// <param name="stateMachine">The state machine.</param>
+ /// <returns>The "boxed" state machine.</returns>
+ private IAsyncStateMachineBox GetStateMachineBox<TStateMachine>(
+ ref TStateMachine stateMachine)
+ where TStateMachine : IAsyncStateMachine
+ {
+ ExecutionContext currentContext = ExecutionContext.Capture();
+
+ // Check first for the most common case: not the first yield in an async method.
+ // In this case, the first yield will have already "boxed" the state machine in
+ // a strongly-typed manner into an AsyncStateMachineBox. It will already contain
+ // the state machine as well as a MoveNextDelegate and a context. The only thing
+ // we might need to do is update the context if that's changed since it was stored.
+ if (m_task is AsyncStateMachineBox<TStateMachine> stronglyTypedBox)
+ {
+ if (stronglyTypedBox.Context != currentContext)
+ {
+ stronglyTypedBox.Context = currentContext;
+ }
+ return stronglyTypedBox;
+ }
+
+ // The least common case: we have a weakly-typed boxed. This results if the debugger
+ // or some other use of reflection accesses a property like ObjectIdForDebugger or a
+ // method like SetNotificationForWaitCompletion prior to the first await happening. In
+ // such situations, we need to get an object to represent the builder, but we don't yet
+ // know the type of the state machine, and thus can't use TStateMachine. Instead, we
+ // use the IAsyncStateMachine interface, which all TStateMachines implement. This will
+ // result in a boxing allocation when storing the TStateMachine if it's a struct, but
+ // this only happens in active debugging scenarios where such performance impact doesn't
+ // matter.
+ if (m_task is AsyncStateMachineBox<IAsyncStateMachine> weaklyTypedBox)
+ {
+ // If this is the first await, we won't yet have a state machine, so store it.
+ if (weaklyTypedBox.StateMachine == null)
+ {
+ Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below
+ weaklyTypedBox.StateMachine = stateMachine;
+ }
+
+ // 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;
+ }
+
+ // 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();
+
+ // At this point, m_task should really be null, in which case we want to create the box.
+ // However, in a variety of debugger-related (erroneous) situations, it might be non-null,
+ // e.g. if the Task property is examined in a Watch window, forcing it to be lazily-intialized
+ // as a Task<TResult> rather than as an AsyncStateMachineBox. The worst that happens in such
+ // cases is we lose the ability to properly step in the debugger, as the debugger uses that
+ // object's identity to track this specific builder/state machine. As such, we proceed to
+ // overwrite whatever's there anyway, even if it's non-null.
+#if CORERT
+ // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because
+ // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes
+ // per each async method in CoreRT / ProjectN binaries without adding much value. Avoid
+ // generating this extra code until a better solution is implemented.
+ var box = new AsyncStateMachineBox<TStateMachine>();
+#else
+ var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
+ CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>() :
+ new AsyncStateMachineBox<TStateMachine>();
+#endif
+ m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
+ box.StateMachine = stateMachine;
+ box.Context = currentContext;
+ return box;
+ }
+
+#if !CORERT
+ // Avoid forcing the JIT to build DebugFinalizableAsyncStateMachineBox<TStateMachine> unless it's actually needed.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static AsyncStateMachineBox<TStateMachine> CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>()
+ where TStateMachine : IAsyncStateMachine =>
+ new DebugFinalizableAsyncStateMachineBox<TStateMachine>();
+
+ /// <summary>
+ /// Provides an async state machine box with a finalizer that will fire an EventSource
+ /// event about the state machine if it's being finalized without having been completed.
+ /// </summary>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ private sealed class DebugFinalizableAsyncStateMachineBox<TStateMachine> : // SOS DumpAsync command depends on this name
+ AsyncStateMachineBox<TStateMachine>
+ where TStateMachine : IAsyncStateMachine
+ {
+ ~DebugFinalizableAsyncStateMachineBox()
+ {
+ // If the state machine is being finalized, something went wrong during its processing,
+ // e.g. it awaited something that got collected without itself having been completed.
+ // Fire an event with details about the state machine to help with debugging.
+ if (!IsCompleted) // double-check it's not completed, just to help minimize false positives
+ {
+ TplEtwProvider.Log.IncompleteAsyncMethod(this);
+ }
+ }
+ }
+#endif
+
+ /// <summary>A strongly-typed box for Task-based async state machines.</summary>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ private class AsyncStateMachineBox<TStateMachine> : // SOS DumpAsync command depends on this name
+ Task<TResult>, IAsyncStateMachineBox
+ where TStateMachine : IAsyncStateMachine
+ {
+ /// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
+ private static readonly ContextCallback s_callback = s =>
+ {
+ Debug.Assert(s is AsyncStateMachineBox<TStateMachine>);
+ // Only used privately to pass directly to EC.Run
+ Unsafe.As<AsyncStateMachineBox<TStateMachine>>(s).StateMachine.MoveNext();
+ };
+
+ /// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
+ private Action _moveNextAction;
+ /// <summary>The state machine itself.</summary>
+ public TStateMachine StateMachine; // mutable struct; do not make this readonly. SOS DumpAsync command depends on this name.
+ /// <summary>Captured ExecutionContext with which to invoke <see cref="MoveNextAction"/>; may be null.</summary>
+ public ExecutionContext Context;
+
+ /// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
+ public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext));
+
+ internal sealed override void ExecuteFromThreadPool(RuntimeThread threadPoolThread) => MoveNext(threadPoolThread);
+
+ /// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
+ public void MoveNext() => MoveNext(threadPoolThread: null);
+
+ private void MoveNext(RuntimeThread threadPoolThread)
+ {
+ Debug.Assert(!IsCompleted);
+
+ bool loggingOn = AsyncCausalitySupport.LoggingOn;
+ if (loggingOn)
+ {
+ AsyncCausalitySupport.TraceSynchronousWorkStart(this);
+ }
+
+ ExecutionContext context = Context;
+ if (context == null)
+ {
+ StateMachine.MoveNext();
+ }
+ else
+ {
+ if (threadPoolThread is null)
+ {
+ ExecutionContext.RunInternal(context, s_callback, this);
+ }
+ else
+ {
+ ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this);
+ }
+ }
+
+ if (IsCompleted)
+ {
+ // Clear out state now that the async method has completed.
+ // This avoids keeping arbitrary state referenced by lifted locals
+ // if this Task / state machine box is held onto.
+ StateMachine = default;
+ Context = default;
+
+#if !CORERT
+ // In case this is a state machine box with a finalizer, suppress its finalization
+ // as it's now complete. We only need the finalizer to run if the box is collected
+ // without having been completed.
+ if (AsyncMethodBuilderCore.TrackAsyncMethodCompletion)
+ {
+ GC.SuppressFinalize(this);
+ }
+#endif
+ }
+
+ if (loggingOn)
+ {
+ AsyncCausalitySupport.TraceSynchronousWorkCompletion();
+ }
+ }
+
+ /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
+ IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging
+ }
+
+ /// <summary>Gets the <see cref="System.Threading.Tasks.Task{TResult}"/> for this builder.</summary>
+ /// <returns>The <see cref="System.Threading.Tasks.Task{TResult}"/> representing the builder's asynchronous operation.</returns>
+ public Task<TResult> Task
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => m_task ?? InitializeTaskAsPromise();
+ }
+
+ /// <summary>
+ /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
+ /// existence when no state machine is needed, e.g. when the builder is being synchronously completed with
+ /// an exception, when the builder is being used out of the context of an async method, etc.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private Task<TResult> InitializeTaskAsPromise()
+ {
+ Debug.Assert(m_task == null);
+ return (m_task = new Task<TResult>());
+ }
+
+ /// <summary>
+ /// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
+ /// existence due to the debugger trying to enable step-out/step-over/etc. prior to the first await yielding
+ /// in an async method. In that case, we don't know the actual TStateMachine type, so we're forced to
+ /// use IAsyncStateMachine instead.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private Task<TResult> InitializeTaskAsStateMachineBox()
+ {
+ Debug.Assert(m_task == null);
+#if CORERT
+ // DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because
+ // it will have a copy of all the slots from its parent. It will add another hundred(s) bytes
+ // per each async method in CoreRT / ProjectN binaries without adding much value. Avoid
+ // generating this extra code until a better solution is implemented.
+ return (m_task = new AsyncStateMachineBox<IAsyncStateMachine>());
+#else
+ return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
+ CreateDebugFinalizableAsyncStateMachineBox<IAsyncStateMachine>() :
+ new AsyncStateMachineBox<IAsyncStateMachine>());
+#endif
+ }
+
+ /// <summary>
+ /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
+ /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state with the specified result.
+ /// </summary>
+ /// <param name="result">The result to use to complete the task.</param>
+ /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
+ 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)
+ {
+ m_task = GetTaskForResult(result);
+ Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null");
+ }
+ else
+ {
+ // Slow path: complete the existing task.
+ SetExistingTaskResult(result);
+ }
+ }
+
+ /// <summary>Completes the already initialized task with the specified result.</summary>
+ /// <param name="result">The result to use to complete the task.</param>
+ private void SetExistingTaskResult(TResult result)
+ {
+ Debug.Assert(m_task != null, "Expected non-null task");
+
+ if (AsyncCausalitySupport.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
+ {
+ LogExistingTaskCompletion();
+ }
+
+ if (!m_task.TrySetResult(result))
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
+ }
+ }
+
+ /// <summary>Handles logging for the successful completion of an operation.</summary>
+ private void LogExistingTaskCompletion()
+ {
+ Debug.Assert(m_task != null);
+
+ if (AsyncCausalitySupport.LoggingOn)
+ {
+ AsyncCausalitySupport.TraceOperationCompletedSuccess(m_task);
+ }
+
+ // 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);
+ }
+ }
+
+ /// <summary>
+ /// Completes the builder by using either the supplied completed task, or by completing
+ /// the builder's previously accessed task using default(TResult).
+ /// </summary>
+ /// <param name="completedTask">A task already completed with the value default(TResult).</param>
+ /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
+ internal void SetResult(Task<TResult> completedTask)
+ {
+ Debug.Assert(completedTask != null, "Expected non-null task");
+ Debug.Assert(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)
+ {
+ m_task = completedTask;
+ }
+ else
+ {
+ // Otherwise, complete the task that's there.
+ SetExistingTaskResult(default);
+ }
+ }
+
+ /// <summary>
+ /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
+ /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
+ /// </summary>
+ /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
+ /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
+ /// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
+ public void SetException(Exception exception)
+ {
+ if (exception == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+ }
+
+ // Get the task, forcing initialization if it hasn't already been initialized.
+ Task<TResult> task = this.Task;
+
+ // If the exception represents cancellation, cancel the task. Otherwise, fault the task.
+ bool successfullySet = exception is OperationCanceledException oce ?
+ task.TrySetCanceled(oce.CancellationToken, oce) :
+ task.TrySetException(exception);
+
+ // 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
+ // return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant
+ // if another thread completes the task first).
+ if (!successfullySet)
+ {
+ ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
+ }
+ }
+
+ /// <summary>
+ /// Called by the debugger to request notification when the first wait operation
+ /// (await, Wait, Result, etc.) on this builder's task completes.
+ /// </summary>
+ /// <param name="enabled">
+ /// true to enable notification; false to disable a previously set notification.
+ /// </param>
+ /// <remarks>
+ /// This should only be invoked from within an asynchronous method,
+ /// and only by the debugger.
+ /// </remarks>
+ internal void SetNotificationForWaitCompletion(bool enabled)
+ {
+ // Get the task (forcing initialization if not already initialized), and set debug notification
+ (m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled);
+
+ // NOTE: It's important that the debugger use builder.SetNotificationForWaitCompletion
+ // rather than builder.Task.SetNotificationForWaitCompletion. Even though the latter will
+ // lazily-initialize the task as well, it'll initialize it to a Task<T> (which is important
+ // to minimize size for cases where an ATMB is used directly by user code to avoid the
+ // allocation overhead of a TaskCompletionSource). If that's done prior to the first await,
+ // the GetMoveNextDelegate code, which needs an AsyncStateMachineBox, will end up creating
+ // a new box and overwriting the previously created task. That'll change the object identity
+ // of the task being used for wait completion notification, and no notification will
+ // ever arrive, breaking step-out behavior when stepping out before the first yielding await.
+ }
+
+ /// <summary>
+ /// Gets an object that may be used to uniquely identify this builder to the debugger.
+ /// </summary>
+ /// <remarks>
+ /// This property lazily instantiates the ID in a non-thread-safe manner.
+ /// 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 or other members that lazily initialize the task.
+ /// </remarks>
+ internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox();
+
+ /// <summary>
+ /// Gets a task for the specified result. This will either
+ /// be a cached or new task, never null.
+ /// </summary>
+ /// <param name="result">The result for which we need a task.</param>
+ /// <returns>The completed task containing the result.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm
+ internal static Task<TResult> GetTaskForResult(TResult result)
+ {
+#if PROJECTN
+ // Currently NUTC does not perform the optimization needed by this method. The result is that
+ // every call to this method results in quite a lot of work, including many allocations, which
+ // is the opposite of the intent. For now, let's just return a new Task each time.
+ // Bug 719350 tracks re-optimizing this in ProjectN.
+#else
+ // The goal of this function is to be give back a cached task if possible,
+ // or to otherwise give back a new task. To give back a cached task,
+ // we need to be able to evaluate the incoming result value, and we need
+ // to avoid as much overhead as possible when doing so, as this function
+ // is invoked as part of the return path from every async method.
+ // Most tasks won't be cached, and thus we need the checks for those that are
+ // to be as close to free as possible. This requires some trickiness given the
+ // lack of generic specialization in .NET.
+ //
+ // Be very careful when modifying this code. It has been tuned
+ // to comply with patterns recognized by both 32-bit and 64-bit JITs.
+ // If changes are made here, be sure to look at the generated assembly, as
+ // small tweaks can have big consequences for what does and doesn't get optimized away.
+ //
+ // Note that this code only ever accesses a static field when it knows it'll
+ // find a cached value, since static fields (even if readonly and integral types)
+ // require special access helpers in this NGEN'd and domain-neutral.
+
+ if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types
+ {
+ // Special case simple value types:
+ // - Boolean
+ // - Byte, SByte
+ // - Char
+ // - Int32, UInt32
+ // - Int64, UInt64
+ // - Int16, UInt16
+ // - IntPtr, UIntPtr
+ // As of .NET 4.5, the (Type)(object)result pattern used below
+ // is recognized and optimized by both 32-bit and 64-bit JITs.
+
+ // For Boolean, we cache all possible values.
+ if (typeof(TResult) == typeof(bool)) // only the relevant branches are kept for each value-type generic instantiation
+ {
+ bool value = (bool)(object)result;
+ Task<bool> task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask;
+ return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed
+ }
+ // For Int32, we cache a range of common values, e.g. [-1,9).
+ else if (typeof(TResult) == typeof(int))
+ {
+ // Compare to constants to avoid static field access if outside of cached range.
+ // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the
+ // lower side, due to positive values being more common than negative as return values.
+ int value = (int)(object)result;
+ if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
+ value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
+ {
+ Task<int> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
+ return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed
+ }
+ }
+ // For other known value types, we only special-case 0 / default(TResult).
+ else if (
+ (typeof(TResult) == typeof(uint) && default == (uint)(object)result) ||
+ (typeof(TResult) == typeof(byte) && default(byte) == (byte)(object)result) ||
+ (typeof(TResult) == typeof(sbyte) && default(sbyte) == (sbyte)(object)result) ||
+ (typeof(TResult) == typeof(char) && default(char) == (char)(object)result) ||
+ (typeof(TResult) == typeof(long) && default == (long)(object)result) ||
+ (typeof(TResult) == typeof(ulong) && default == (ulong)(object)result) ||
+ (typeof(TResult) == typeof(short) && default(short) == (short)(object)result) ||
+ (typeof(TResult) == typeof(ushort) && default(ushort) == (ushort)(object)result) ||
+ (typeof(TResult) == typeof(IntPtr) && default == (IntPtr)(object)result) ||
+ (typeof(TResult) == typeof(UIntPtr) && default == (UIntPtr)(object)result))
+ {
+ return s_defaultResultTask;
+ }
+ }
+ else if (result == null) // optimized away for value types
+ {
+ return s_defaultResultTask;
+ }
+#endif
+
+ // No cached task is available. Manufacture a new one for this result.
+ return new Task<TResult>(result);
+ }
+ }
+
+ /// <summary>Provides a cache of closed generic tasks for async methods.</summary>
+ internal static class AsyncTaskCache
+ {
+#if !PROJECTN
+ // All static members are initialized inline to ensure type is beforefieldinit
+
+ /// <summary>A cached Task{Boolean}.Result == true.</summary>
+ internal readonly static Task<bool> TrueTask = CreateCacheableTask(true);
+ /// <summary>A cached Task{Boolean}.Result == false.</summary>
+ internal readonly static Task<bool> FalseTask = CreateCacheableTask(false);
+
+ /// <summary>The cache of Task{Int32}.</summary>
+ internal readonly static Task<int>[] Int32Tasks = CreateInt32Tasks();
+ /// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
+ internal const int INCLUSIVE_INT32_MIN = -1;
+ /// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
+ internal const int EXCLUSIVE_INT32_MAX = 9;
+ /// <summary>Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).</summary>
+ private static Task<int>[] CreateInt32Tasks()
+ {
+ Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min");
+ var tasks = new Task<int>[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN];
+ for (int i = 0; i < tasks.Length; i++)
+ {
+ tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN);
+ }
+ return tasks;
+ }
+#endif
+
+ /// <summary>Creates a non-disposable task.</summary>
+ /// <typeparam name="TResult">Specifies the result type.</typeparam>
+ /// <param name="result">The result for the task.</param>
+ /// <returns>The cacheable task.</returns>
+ internal static Task<TResult> CreateCacheableTask<TResult>(TResult result) =>
+ new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
+ }
+
+ /// <summary>
+ /// An interface implemented by all <see cref="AsyncTaskMethodBuilder{TResult}.AsyncStateMachineBox{TStateMachine}"/> instances, regardless of generics.
+ /// </summary>
+ internal interface IAsyncStateMachineBox
+ {
+ /// <summary>Move the state machine forward.</summary>
+ void MoveNext();
+
+ /// <summary>
+ /// Gets an action for moving forward the contained state machine.
+ /// This will lazily-allocate the delegate as needed.
+ /// </summary>
+ Action MoveNextAction { get; }
+
+ /// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
+ IAsyncStateMachine GetStateMachineObject();
+ }
+
+ /// <summary>Shared helpers for manipulating state related to async state machines.</summary>
+ internal static class AsyncMethodBuilderCore // debugger depends on this exact name
+ {
+ /// <summary>Initiates the builder's execution with the associated state machine.</summary>
+ /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
+ /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+ [DebuggerStepThrough]
+ public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
+ {
+ if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
+ }
+
+ // enregistrer variables with 0 post-fix so they can be used in registers without EH forcing them to stack
+ // Capture references to Thread Contexts
+ RuntimeThread currentThread0 = RuntimeThread.CurrentThread;
+ RuntimeThread currentThread = currentThread0;
+ ExecutionContext previousExecutionCtx0 = currentThread0.ExecutionContext;
+
+ // Store current ExecutionContext and SynchronizationContext as "previousXxx".
+ // This allows us to restore them and undo any Context changes made in stateMachine.MoveNext
+ // so that they won't "leak" out of the first await.
+ ExecutionContext previousExecutionCtx = previousExecutionCtx0;
+ SynchronizationContext previousSyncCtx = currentThread0.SynchronizationContext;
+
+ try
+ {
+ stateMachine.MoveNext();
+ }
+ finally
+ {
+ // Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
+ SynchronizationContext previousSyncCtx1 = previousSyncCtx;
+ RuntimeThread currentThread1 = currentThread;
+ // The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
+ if (previousSyncCtx1 != currentThread1.SynchronizationContext)
+ {
+ // Restore changed SynchronizationContext back to previous
+ currentThread1.SynchronizationContext = previousSyncCtx1;
+ }
+
+ ExecutionContext previousExecutionCtx1 = previousExecutionCtx;
+ ExecutionContext currentExecutionCtx1 = currentThread1.ExecutionContext;
+ if (previousExecutionCtx1 != currentExecutionCtx1)
+ {
+ ExecutionContext.RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1);
+ }
+ }
+ }
+
+#if !CORERT
+ /// <summary>Gets whether we should be tracking async method completions for eventing.</summary>
+ internal static bool TrackAsyncMethodCompletion
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => TplEtwProvider.Log.IsEnabled(EventLevel.Warning, TplEtwProvider.Keywords.AsyncMethod);
+ }
+#endif
+
+ /// <summary>Gets a description of the state of the state machine object, suitable for debug purposes.</summary>
+ /// <param name="stateMachine">The state machine object.</param>
+ /// <returns>A description of the state machine.</returns>
+ internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateMachine)
+ {
+ Debug.Assert(stateMachine != null);
+
+ Type stateMachineType = stateMachine.GetType();
+ FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ var sb = new StringBuilder();
+ sb.AppendLine(stateMachineType.FullName);
+ foreach (FieldInfo fi in fields)
+ {
+ sb.AppendLine($" {fi.Name}: {fi.GetValue(stateMachine)}");
+ }
+ return sb.ToString();
+ }
+
+ internal static Action CreateContinuationWrapper(Action continuation, Action<Action,Task> invokeAction, Task innerTask) =>
+ new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
+
+ /// <summary>This helper routine is targeted by the debugger. Its purpose is to remove any delegate wrappers introduced by
+ /// the framework that the debugger doesn't want to see.</summary>
+#if PROJECTN
+ [DependencyReductionRoot]
+#endif
+ internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature
+ {
+ object target = action.Target;
+ return
+ target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
+ target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
+ action;
+ }
+
+ internal static Task TryGetContinuationTask(Action continuation) =>
+ (continuation?.Target as ContinuationWrapper)?._innerTask;
+
+ /// <summary>Throws the exception on the ThreadPool.</summary>
+ /// <param name="exception">The exception to propagate.</param>
+ /// <param name="targetContext">The target context on which to propagate the exception. Null to use the ThreadPool.</param>
+ internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
+ {
+ // Capture the exception into an ExceptionDispatchInfo so that its
+ // stack trace and Watson bucket info will be preserved
+ var edi = ExceptionDispatchInfo.Capture(exception);
+
+ // If the user supplied a SynchronizationContext...
+ if (targetContext != null)
+ {
+ try
+ {
+ // Post the throwing of the exception to that context, and return.
+ targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
+ return;
+ }
+ catch (Exception postException)
+ {
+ // If something goes horribly wrong in the Post, we'll
+ // propagate both exceptions on the ThreadPool
+ edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
+ }
+ }
+
+#if CORERT
+ RuntimeAugments.ReportUnhandledException(edi.SourceException);
+#else
+
+#if FEATURE_COMINTEROP
+ // If we have the new error reporting APIs, report this error.
+ if (WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException))
+ return;
+#endif // FEATURE_COMINTEROP
+
+ // Propagate the exception(s) on the ThreadPool
+ ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi);
+
+#endif // CORERT
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ private sealed class ContinuationWrapper
+ {
+ private readonly Action<Action, Task> _invokeAction; // This wrapper is an action that wraps another action, this is that Action.
+ internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
+ internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
+
+ internal ContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask)
+ {
+ Debug.Assert(continuation != null, "Expected non-null continuation");
+ Debug.Assert(invokeAction != null, "Expected non-null continuation");
+
+ _invokeAction = invokeAction;
+ _continuation = continuation;
+ _innerTask = innerTask ?? TryGetContinuationTask(continuation); // if we don't have a task, see if our continuation is a wrapper and use that.
+ }
+
+ internal void Invoke() => _invokeAction(_continuation, _innerTask);
+ }
+ }
+}