From 0c789affed0f3f82090645527f46a3713f41f913 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 25 Aug 2019 22:10:37 -0400 Subject: [PATCH] Separate Async*MethodBuilder types into their own files (dotnet/coreclr#26364) * Separate async method builder types into their own files * Minor cleanup of AsyncTaskMethodBuilder types * Split AsyncValueTaskMethodBuilder types into their own files Commit migrated from https://github.com/dotnet/coreclr/commit/def4f696fbfde6ff05c3a4d88f092e21e684ea0f --- .../src/System.Private.CoreLib.Shared.projitems | 8 +- .../CompilerServices/AsyncMethodBuilderCore.cs | 156 +++++++ .../Runtime/CompilerServices/AsyncTaskCache.cs | 46 ++ .../CompilerServices/AsyncTaskMethodBuilder.cs | 121 +++++ ...MethodBuilder.cs => AsyncTaskMethodBuilderT.cs} | 486 +-------------------- .../AsyncValueTaskMethodBuilder.cs | 96 ---- .../AsyncValueTaskMethodBuilderT.cs | 105 +++++ .../CompilerServices/AsyncVoidMethodBuilder.cs | 163 +++++++ .../CompilerServices/IAsyncStateMachineBox.cs | 24 + 9 files changed, 629 insertions(+), 576 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskCache.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilder.cs rename src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/{AsyncMethodBuilder.cs => AsyncTaskMethodBuilderT.cs} (56%) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilderT.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncVoidMethodBuilder.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IAsyncStateMachineBox.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 65c68d8..6b6d19a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -544,10 +544,15 @@ - + + + + + + @@ -573,6 +578,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs new file mode 100644 index 0000000..115c03b --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilderCore.cs @@ -0,0 +1,156 @@ +// 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. + +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Text; + +namespace System.Runtime.CompilerServices +{ + /// Shared helpers for manipulating state related to async state machines. + internal static class AsyncMethodBuilderCore // debugger depends on this exact name + { + /// 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 static void Start(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 + Thread currentThread0 = Thread.CurrentThread; + Thread 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; + Thread 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); + } + } + } + + public static void SetStateMachine(IAsyncStateMachine stateMachine, Task task) + { + if (stateMachine == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine); + } + + if (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."); + } + +#if !CORERT + /// Gets whether we should be tracking async method completions for eventing. + internal static bool TrackAsyncMethodCompletion + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => TplEventSource.Log.IsEnabled(EventLevel.Warning, TplEventSource.Keywords.AsyncMethod); + } +#endif + + /// Gets a description of the state of the state machine object, suitable for debug purposes. + /// The state machine object. + /// A description of the state machine. + 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 invokeAction, Task innerTask) => + new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke; + + /// 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. + 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; + + /// + /// 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 sealed class ContinuationWrapper // SOS DumpAsync command depends on this name + { + private readonly Action _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). SOS DumpAsync command depends on this name. + 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 invokeAction, Task innerTask) + { + Debug.Assert(continuation != null, "Expected non-null continuation"); + Debug.Assert(invokeAction != null, "Expected non-null invokeAction"); + Debug.Assert(innerTask != null, "Expected non-null innerTask"); + + _invokeAction = invokeAction; + _continuation = continuation; + _innerTask = innerTask; + } + + internal void Invoke() => _invokeAction(_continuation, _innerTask); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskCache.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskCache.cs new file mode 100644 index 0000000..9da2fc0 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskCache.cs @@ -0,0 +1,46 @@ +// 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. + +using System.Diagnostics; +using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; + +namespace System.Runtime.CompilerServices +{ + /// Provides a cache of closed generic tasks for async methods. + internal static class AsyncTaskCache + { + /// A cached Task{Boolean}.Result == true. + internal static readonly Task s_trueTask = CreateCacheableTask(true); + /// A cached Task{Boolean}.Result == false. + internal static readonly Task s_falseTask = CreateCacheableTask(false); + /// The cache of Task{Int32}. + internal static readonly Task[] s_int32Tasks = CreateInt32Tasks(); + /// The minimum value, inclusive, for which we want a cached task. + internal const int InclusiveInt32Min = -1; + /// The maximum value, exclusive, for which we want a cached task. + internal const int ExclusiveInt32Max = 9; + + /// Creates a non-disposable task. + /// Specifies the result type. + /// The result for the task. + /// The cacheable task. + internal static Task CreateCacheableTask([AllowNull] TResult result) => + new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default); + + /// Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). + private static Task[] CreateInt32Tasks() + { + Debug.Assert(ExclusiveInt32Max >= InclusiveInt32Min, "Expected max to be at least min"); + + var tasks = new Task[ExclusiveInt32Max - InclusiveInt32Min]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = CreateCacheableTask(i + InclusiveInt32Min); + } + + return tasks; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilder.cs new file mode 100644 index 0000000..83f00fe --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilder.cs @@ -0,0 +1,121 @@ +// 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. + +using System.Diagnostics; +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices +{ + /// + /// Provides a builder for asynchronous methods that return . + /// This type is intended for compiler use only. + /// + /// + /// 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. + /// + public struct AsyncTaskMethodBuilder + { + /// A cached VoidTaskResult task used for builders that complete synchronously. + private static readonly 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. Debugger depends on the exact name of this field. + + /// Initializes a new . + /// The initialized . + public static AsyncTaskMethodBuilder Create() => + // m_builder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr + // that Create() is a nop, so we can just return the default here. + default; + + /// 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] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => + AsyncMethodBuilderCore.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); + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter. + /// The state machine. + public void AwaitOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine => + m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine); + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter. + /// The state machine. + public void AwaitUnsafeOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine => + m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + + /// Gets the for this builder. + /// The representing the builder's asynchronous operation. + /// The builder is not initialized. + public Task Task + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => m_builder.Task; + } + + /// + /// Completes the in the + /// RanToCompletion state. + /// + /// The builder is not initialized. + /// The task has already completed. + public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask. + + /// + /// Completes the in the + /// Faulted state with the specified exception. + /// + /// The to use to fault the task. + /// 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); + + /// + /// Called by the debugger to request notification when the first wait operation + /// (await, Wait, Result, etc.) on this builder's task completes. + /// + /// + /// true to enable notification; false to disable a previously set notification. + /// + internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled); + + /// + /// Gets an object that may be used to uniquely identify this builder to the debugger. + /// + /// + /// 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. + /// + internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs similarity index 56% rename from src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs index ba0b52e..8bbf383 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @@ -2,291 +2,15 @@ // 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.Threading; using System.Threading.Tasks; -using System.Text; using Internal.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; namespace System.Runtime.CompilerServices { /// - /// Provides a builder for asynchronous methods that return void. - /// This type is intended for compiler use only. - /// - public struct AsyncVoidMethodBuilder - { - /// The synchronization context associated with this operation. - 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() - { - SynchronizationContext? sc = SynchronizationContext.Current; - sc?.OperationStarted(); - - // _builder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr - // that Create() is a nop, so we can just return the default here. - return new AsyncVoidMethodBuilder() { _synchronizationContext = sc }; - } - - /// Initiates the builder's execution with the associated state machine. - /// Specifies the type of the state machine. - /// The state machine instance, passed by reference. - /// The argument was null (Nothing in Visual Basic). - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => - AsyncMethodBuilderCore.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) => - _builder.SetStateMachine(stateMachine); - - /// - /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. - /// - /// Specifies the type of the awaiter. - /// Specifies the type of the state machine. - /// The awaiter. - /// The state machine. - public void AwaitOnCompleted( - ref TAwaiter awaiter, ref TStateMachine stateMachine) - where TAwaiter : INotifyCompletion - where TStateMachine : IAsyncStateMachine => - _builder.AwaitOnCompleted(ref awaiter, ref stateMachine); - - /// - /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. - /// - /// Specifies the type of the awaiter. - /// Specifies the type of the state machine. - /// The awaiter. - /// The state machine. - public void AwaitUnsafeOnCompleted( - ref TAwaiter awaiter, ref TStateMachine stateMachine) - where TAwaiter : ICriticalNotifyCompletion - where TStateMachine : IAsyncStateMachine => - _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); - - /// Completes the method builder successfully. - public void SetResult() - { - if (AsyncCausalityTracer.LoggingOn) - { - AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Completed); - } - - // 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(); - } - } - - /// Faults the method builder with an exception. - /// The exception that is the cause of this fault. - /// The argument is null (Nothing in Visual Basic). - /// The builder is not initialized. - public void SetException(Exception exception) - { - if (exception == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); - } - - if (AsyncCausalityTracer.LoggingOn) - { - AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Error); - } - - if (_synchronizationContext != null) - { - // If we captured a synchronization context, Post the throwing of the exception to it - // and decrement its outstanding operation count. - try - { - System.Threading.Tasks.Task.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. - System.Threading.Tasks.Task.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(); - } - - /// Notifies the current synchronization context that the operation completed. - 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. - Task.ThrowAsync(exc, targetContext: null); - } - } - - /// Lazily instantiate the Task in a non-thread-safe manner. - private Task Task => _builder.Task; - - /// - /// Gets an object that may be used to uniquely identify this builder to the debugger. - /// - /// - /// 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. - /// - internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger; - } - - /// - /// Provides a builder for asynchronous methods that return . - /// This type is intended for compiler use only. - /// - /// - /// 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. - /// - public struct AsyncTaskMethodBuilder - { - /// A cached VoidTaskResult task used for builders that complete synchronously. - private static readonly 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. Debugger depends on the exact name of this field. - - /// Initializes a new . - /// The initialized . - public static AsyncTaskMethodBuilder Create() => - // m_builder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr - // that Create() is a nop, so we can just return the default here. - default; - - /// 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] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => - AsyncMethodBuilderCore.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); - - /// - /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. - /// - /// Specifies the type of the awaiter. - /// Specifies the type of the state machine. - /// The awaiter. - /// The state machine. - public void AwaitOnCompleted( - ref TAwaiter awaiter, ref TStateMachine stateMachine) - where TAwaiter : INotifyCompletion - where TStateMachine : IAsyncStateMachine => - m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine); - - /// - /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. - /// - /// Specifies the type of the awaiter. - /// Specifies the type of the state machine. - /// The awaiter. - /// The state machine. - public void AwaitUnsafeOnCompleted( - ref TAwaiter awaiter, ref TStateMachine stateMachine) - where TAwaiter : ICriticalNotifyCompletion - where TStateMachine : IAsyncStateMachine => - m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); - - /// Gets the for this builder. - /// The representing the builder's asynchronous operation. - /// The builder is not initialized. - public Task Task - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => m_builder.Task; - } - - /// - /// Completes the in the - /// RanToCompletion state. - /// - /// The builder is not initialized. - /// The task has already completed. - public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask. - - /// - /// Completes the in the - /// Faulted state with the specified exception. - /// - /// The to use to fault the task. - /// 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); - - /// - /// Called by the debugger to request notification when the first wait operation - /// (await, Wait, Result, etc.) on this builder's task completes. - /// - /// - /// true to enable notification; false to disable a previously set notification. - /// - internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled); - - /// - /// Gets an object that may be used to uniquely identify this builder to the debugger. - /// - /// - /// 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. - /// - internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger; - } - - /// /// Provides a builder for asynchronous methods that return . /// This type is intended for compiler use only. /// @@ -554,12 +278,13 @@ namespace System.Runtime.CompilerServices /// A delegate to the method. private Action? _moveNextAction; /// The state machine itself. - [AllowNull, MaybeNull] public TStateMachine StateMachine = default; // mutable struct; do not make this readonly. SOS DumpAsync command depends on this name. + [AllowNull, MaybeNull] + public TStateMachine StateMachine = default; // mutable struct; do not make this readonly. SOS DumpAsync command depends on this name. /// Captured ExecutionContext with which to invoke ; may be null. public ExecutionContext? Context; /// A delegate to the method. - public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext)); + public Action MoveNextAction => _moveNextAction ??= new Action(MoveNext); internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread) => MoveNext(threadPoolThread); @@ -850,7 +575,7 @@ namespace System.Runtime.CompilerServices if (typeof(TResult) == typeof(bool)) // only the relevant branches are kept for each value-type generic instantiation { bool value = (bool)(object)result!; - Task task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask; + Task task = value ? AsyncTaskCache.s_trueTask : AsyncTaskCache.s_falseTask; return Unsafe.As>(task); // UnsafeCast avoids type check we know will succeed } // For Int32, we cache a range of common values, e.g. [-1,9). @@ -860,10 +585,10 @@ namespace System.Runtime.CompilerServices // 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) + if (value < AsyncTaskCache.ExclusiveInt32Max && + value >= AsyncTaskCache.InclusiveInt32Min) { - Task task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN]; + Task task = AsyncTaskCache.s_int32Tasks[value - AsyncTaskCache.InclusiveInt32Min]; return Unsafe.As>(task); // UnsafeCast avoids a type check we know will succeed } } @@ -892,201 +617,4 @@ namespace System.Runtime.CompilerServices return new Task(result); } } - - /// Provides a cache of closed generic tasks for async methods. - internal static class AsyncTaskCache - { - // All static members are initialized inline to ensure type is beforefieldinit - - /// A cached Task{Boolean}.Result == true. - internal static readonly Task TrueTask = CreateCacheableTask(true); - /// A cached Task{Boolean}.Result == false. - internal static readonly Task FalseTask = CreateCacheableTask(false); - - /// The cache of Task{Int32}. - internal static readonly Task[] Int32Tasks = CreateInt32Tasks(); - /// The minimum value, inclusive, for which we want a cached task. - internal const int INCLUSIVE_INT32_MIN = -1; - /// The maximum value, exclusive, for which we want a cached task. - internal const int EXCLUSIVE_INT32_MAX = 9; - /// Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - private static Task[] CreateInt32Tasks() - { - Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min"); - var tasks = new Task[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN]; - for (int i = 0; i < tasks.Length; i++) - { - tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN); - } - return tasks; - } - - /// Creates a non-disposable task. - /// Specifies the result type. - /// The result for the task. - /// The cacheable task. - internal static Task CreateCacheableTask([AllowNull] TResult result) => - new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default); - } - - /// - /// An interface implemented by all instances, regardless of generics. - /// - internal interface IAsyncStateMachineBox - { - /// Move the state machine forward. - void MoveNext(); - - /// - /// Gets an action for moving forward the contained state machine. - /// This will lazily-allocate the delegate as needed. - /// - Action MoveNextAction { get; } - - /// Gets the state machine as a boxed object. This should only be used for debugging purposes. - IAsyncStateMachine GetStateMachineObject(); - } - - /// Shared helpers for manipulating state related to async state machines. - internal static class AsyncMethodBuilderCore // debugger depends on this exact name - { - /// 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 static void Start(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 - Thread currentThread0 = Thread.CurrentThread; - Thread 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; - Thread 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); - } - } - } - - public static void SetStateMachine(IAsyncStateMachine stateMachine, Task task) - { - if (stateMachine == null) - { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine); - } - - if (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."); - } - -#if !CORERT - /// Gets whether we should be tracking async method completions for eventing. - internal static bool TrackAsyncMethodCompletion - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => TplEventSource.Log.IsEnabled(EventLevel.Warning, TplEventSource.Keywords.AsyncMethod); - } -#endif - - /// Gets a description of the state of the state machine object, suitable for debug purposes. - /// The state machine object. - /// A description of the state machine. - 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 invokeAction, Task innerTask) => - new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke; - - /// 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. - 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; - - /// - /// 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 sealed class ContinuationWrapper // SOS DumpAsync command depends on this name - { - private readonly Action _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). SOS DumpAsync command depends on this name. - 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 invokeAction, Task innerTask) - { - Debug.Assert(continuation != null, "Expected non-null continuation"); - Debug.Assert(invokeAction != null, "Expected non-null invokeAction"); - Debug.Assert(innerTask != null, "Expected non-null innerTask"); - - _invokeAction = invokeAction; - _continuation = continuation; - _innerTask = innerTask; - } - - internal void Invoke() => _invokeAction(_continuation, _innerTask); - } - } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs index ffd9753..f5ea29b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs @@ -97,100 +97,4 @@ namespace System.Runtime.CompilerServices _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); } } - - /// Represents a builder for asynchronous methods that returns a . - /// The type of the result. - [StructLayout(LayoutKind.Auto)] - public struct AsyncValueTaskMethodBuilder - { - /// The to which most operations are delegated. - private AsyncTaskMethodBuilder _methodBuilder; // mutable struct; do not make it readonly - /// The result for this builder, if it's completed before any awaits occur. - private TResult _result; - /// true if contains the synchronous result for the async method; otherwise, false. - private bool _haveResult; - /// true if the builder should be used for setting/getting the result; otherwise, false. - private bool _useBuilder; - - /// Creates an instance of the struct. - /// The initialized instance. - public static AsyncValueTaskMethodBuilder Create() => - // _methodBuilder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr - // that Create() is a nop, so we can just return the default here. - default; - - /// Begins running the builder with the associated state machine. - /// The type of the state machine. - /// The state machine instance, passed by reference. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => - // will provide the right ExecutionContext semantics - AsyncMethodBuilderCore.Start(ref stateMachine); - - /// Associates the builder with the specified state machine. - /// The state machine instance to associate with the builder. - public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine); - - /// Marks the task as successfully completed. - /// The result to use to complete the task. - public void SetResult(TResult result) - { - if (_useBuilder) - { - _methodBuilder.SetResult(result); - } - else - { - _result = result; - _haveResult = true; - } - } - - /// Marks the task as failed and binds the specified exception to the task. - /// The exception to bind to the task. - public void SetException(Exception exception) => _methodBuilder.SetException(exception); - - /// Gets the task for this builder. - public ValueTask Task - { - get - { - if (_haveResult) - { - return new ValueTask(_result); - } - else - { - _useBuilder = true; - return new ValueTask(_methodBuilder.Task); - } - } - } - - /// Schedules the state machine to proceed to the next action when the specified awaiter completes. - /// The type of the awaiter. - /// The type of the state machine. - /// the awaiter - /// The state machine. - public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) - where TAwaiter : INotifyCompletion - where TStateMachine : IAsyncStateMachine - { - _useBuilder = true; - _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); - } - - /// Schedules the state machine to proceed to the next action when the specified awaiter completes. - /// The type of the awaiter. - /// The type of the state machine. - /// the awaiter - /// The state machine. - public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) - where TAwaiter : ICriticalNotifyCompletion - where TStateMachine : IAsyncStateMachine - { - _useBuilder = true; - _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); - } - } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilderT.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilderT.cs new file mode 100644 index 0000000..fc5ce80 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilderT.cs @@ -0,0 +1,105 @@ +// 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. + +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices +{ + /// Represents a builder for asynchronous methods that returns a . + /// The type of the result. + [StructLayout(LayoutKind.Auto)] + public struct AsyncValueTaskMethodBuilder + { + /// The to which most operations are delegated. + private AsyncTaskMethodBuilder _methodBuilder; // mutable struct; do not make it readonly + /// The result for this builder, if it's completed before any awaits occur. + private TResult _result; + /// true if contains the synchronous result for the async method; otherwise, false. + private bool _haveResult; + /// true if the builder should be used for setting/getting the result; otherwise, false. + private bool _useBuilder; + + /// Creates an instance of the struct. + /// The initialized instance. + public static AsyncValueTaskMethodBuilder Create() => + // _methodBuilder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr + // that Create() is a nop, so we can just return the default here. + default; + + /// Begins running the builder with the associated state machine. + /// The type of the state machine. + /// The state machine instance, passed by reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => + // will provide the right ExecutionContext semantics + AsyncMethodBuilderCore.Start(ref stateMachine); + + /// Associates the builder with the specified state machine. + /// The state machine instance to associate with the builder. + public void SetStateMachine(IAsyncStateMachine stateMachine) => _methodBuilder.SetStateMachine(stateMachine); + + /// Marks the task as successfully completed. + /// The result to use to complete the task. + public void SetResult(TResult result) + { + if (_useBuilder) + { + _methodBuilder.SetResult(result); + } + else + { + _result = result; + _haveResult = true; + } + } + + /// Marks the task as failed and binds the specified exception to the task. + /// The exception to bind to the task. + public void SetException(Exception exception) => _methodBuilder.SetException(exception); + + /// Gets the task for this builder. + public ValueTask Task + { + get + { + if (_haveResult) + { + return new ValueTask(_result); + } + else + { + _useBuilder = true; + return new ValueTask(_methodBuilder.Task); + } + } + } + + /// Schedules the state machine to proceed to the next action when the specified awaiter completes. + /// The type of the awaiter. + /// The type of the state machine. + /// the awaiter + /// The state machine. + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); + } + + /// Schedules the state machine to proceed to the next action when the specified awaiter completes. + /// The type of the awaiter. + /// The type of the state machine. + /// the awaiter + /// The state machine. + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine + { + _useBuilder = true; + _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncVoidMethodBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncVoidMethodBuilder.cs new file mode 100644 index 0000000..3a0a0a2 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncVoidMethodBuilder.cs @@ -0,0 +1,163 @@ +// 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. + +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices +{ + /// + /// Provides a builder for asynchronous methods that return void. + /// This type is intended for compiler use only. + /// + public struct AsyncVoidMethodBuilder + { + /// The synchronization context associated with this operation. + 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() + { + SynchronizationContext? sc = SynchronizationContext.Current; + sc?.OperationStarted(); + + // _builder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr + // that Create() is a nop, so we can just return the default here. + return new AsyncVoidMethodBuilder() { _synchronizationContext = sc }; + } + + /// Initiates the builder's execution with the associated state machine. + /// Specifies the type of the state machine. + /// The state machine instance, passed by reference. + /// The argument was null (Nothing in Visual Basic). + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine => + AsyncMethodBuilderCore.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) => + _builder.SetStateMachine(stateMachine); + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter. + /// The state machine. + public void AwaitOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : INotifyCompletion + where TStateMachine : IAsyncStateMachine => + _builder.AwaitOnCompleted(ref awaiter, ref stateMachine); + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter. + /// The state machine. + public void AwaitUnsafeOnCompleted( + ref TAwaiter awaiter, ref TStateMachine stateMachine) + where TAwaiter : ICriticalNotifyCompletion + where TStateMachine : IAsyncStateMachine => + _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); + + /// Completes the method builder successfully. + public void SetResult() + { + if (AsyncCausalityTracer.LoggingOn) + { + AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Completed); + } + + // 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(); + } + } + + /// Faults the method builder with an exception. + /// The exception that is the cause of this fault. + /// The argument is null (Nothing in Visual Basic). + /// The builder is not initialized. + public void SetException(Exception exception) + { + if (exception == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); + } + + if (AsyncCausalityTracer.LoggingOn) + { + AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Error); + } + + if (_synchronizationContext != null) + { + // If we captured a synchronization context, Post the throwing of the exception to it + // and decrement its outstanding operation count. + try + { + System.Threading.Tasks.Task.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. + System.Threading.Tasks.Task.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(); + } + + /// Notifies the current synchronization context that the operation completed. + 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. + Task.ThrowAsync(exc, targetContext: null); + } + } + + /// Lazily instantiate the Task in a non-thread-safe manner. + private Task Task => _builder.Task; + + /// + /// Gets an object that may be used to uniquely identify this builder to the debugger. + /// + /// + /// 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. + /// + internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IAsyncStateMachineBox.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IAsyncStateMachineBox.cs new file mode 100644 index 0000000..bb09d35 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IAsyncStateMachineBox.cs @@ -0,0 +1,24 @@ +// 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. + +namespace System.Runtime.CompilerServices +{ + /// + /// An interface implemented by all instances, regardless of generics. + /// + internal interface IAsyncStateMachineBox + { + /// Move the state machine forward. + void MoveNext(); + + /// + /// Gets an action for moving forward the contained state machine. + /// This will lazily-allocate the delegate as needed. + /// + Action MoveNextAction { get; } + + /// Gets the state machine as a boxed object. This should only be used for debugging purposes. + IAsyncStateMachine GetStateMachineObject(); + } +} -- 2.7.4