From d5ee1cb9a2d4dc58d85ed9e59e1002f798234277 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 17 Jan 2019 05:42:23 +0100 Subject: Add new members to AsyncCausalitySupport. Remove file moved to shared partition. Fix builds without FeatureCominterop. --- .../System.Private.CoreLib.csproj | 3 +- .../Threading/Tasks/AsyncCausalitySupport.cs | 12 + .../Runtime/CompilerServices/AsyncMethodBuilder.cs | 1087 -------------------- 3 files changed, 13 insertions(+), 1089 deletions(-) delete mode 100644 src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs diff --git a/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/System.Private.CoreLib.csproj index ca8cbcb9e9..ef55b658d9 100644 --- a/src/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -121,6 +121,7 @@ + @@ -243,7 +244,6 @@ - @@ -304,7 +304,6 @@ - diff --git a/src/System.Private.CoreLib/src/Internal/Threading/Tasks/AsyncCausalitySupport.cs b/src/System.Private.CoreLib/src/Internal/Threading/Tasks/AsyncCausalitySupport.cs index b16157f8be..df34a64eff 100644 --- a/src/System.Private.CoreLib/src/Internal/Threading/Tasks/AsyncCausalitySupport.cs +++ b/src/System.Private.CoreLib/src/Internal/Threading/Tasks/AsyncCausalitySupport.cs @@ -57,6 +57,18 @@ namespace Internal.Threading.Tasks { AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, task.Id, AsyncCausalityStatus.Error); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void TraceSynchronousWorkStart(Task task) + { + AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, task.Id, CausalitySynchronousWork.Execution); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void TraceSynchronousWorkCompletion() + { + AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); + } } } diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs deleted file mode 100644 index 44daf32c19..0000000000 --- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ /dev/null @@ -1,1087 +0,0 @@ -// 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; - -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(); - 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(CausalityTraceLevel.Required, this.Task.Id, 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(CausalityTraceLevel.Required, this.Task.Id, 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 - { - 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(); - } - - /// 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. - AsyncMethodBuilderCore.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 readonly static Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask; - - /// The generic builder object to which this non-generic instance delegates. - private AsyncTaskMethodBuilder m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field. - - /// Initializes a new . - /// The initialized . - public static AsyncTaskMethodBuilder Create() => 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. - /// - /// - /// 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. - /// - public struct AsyncTaskMethodBuilder - { - /// A cached task for default(TResult). - internal readonly static Task s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult)); - - /// The lazily-initialized built task. - private Task m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field. - - /// Initializes a new . - /// The initialized . - public static AsyncTaskMethodBuilder 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. - } - - /// 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) - { - 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."); - } - - /// - /// 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 - { - try - { - awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction); - } - catch (Exception e) - { - AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); - } - } - - /// - /// 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 - { - 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(ref awaiter); // relies on TaskAwaiter/TaskAwaiter 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(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); - } - } - } - - /// Gets the "boxed" state machine object. - /// Specifies the type of the async state machine. - /// The state machine. - /// The "boxed" state machine. - private IAsyncStateMachineBox GetStateMachineBox( - 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 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 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 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. - var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ? - CreateDebugFinalizableAsyncStateMachineBox() : - new AsyncStateMachineBox(); - m_task = box; // important: this must be done before storing stateMachine into box.StateMachine! - box.StateMachine = stateMachine; - box.Context = currentContext; - return box; - } - - // Avoid forcing the JIT to build DebugFinalizableAsyncStateMachineBox unless it's actually needed. - [MethodImpl(MethodImplOptions.NoInlining)] - private static AsyncStateMachineBox CreateDebugFinalizableAsyncStateMachineBox() - where TStateMachine : IAsyncStateMachine => - new DebugFinalizableAsyncStateMachineBox(); - - /// - /// 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. - /// - /// Specifies the type of the state machine. - private sealed class DebugFinalizableAsyncStateMachineBox : // SOS DumpAsync command depends on this name - AsyncStateMachineBox - 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); - } - } - } - - /// A strongly-typed box for Task-based async state machines. - /// Specifies the type of the state machine. - private class AsyncStateMachineBox : // SOS DumpAsync command depends on this name - Task, IAsyncStateMachineBox - where TStateMachine : IAsyncStateMachine - { - /// Delegate used to invoke on an ExecutionContext when passed an instance of this box type. - private static readonly ContextCallback s_callback = s => - { - Debug.Assert(s is AsyncStateMachineBox); - // Only used privately to pass directly to EC.Run - Unsafe.As>(s).StateMachine.MoveNext(); - }; - - /// A delegate to the method. - private Action _moveNextAction; - /// The state machine itself. - public TStateMachine StateMachine; // 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)); - - internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread) => MoveNext(threadPoolThread); - - /// Calls MoveNext on - public void MoveNext() => MoveNext(threadPoolThread: null); - - private void MoveNext(Thread threadPoolThread) - { - Debug.Assert(!IsCompleted); - - bool loggingOn = AsyncCausalityTracer.LoggingOn; - if (loggingOn) - { - AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, Id, CausalitySynchronousWork.Execution); - } - - 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; - - // 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); - } - } - - if (loggingOn) - { - AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); - } - } - - /// Gets the state machine as a boxed object. This should only be used for debugging purposes. - IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging - } - - /// Gets the for this builder. - /// The representing the builder's asynchronous operation. - public Task Task - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => m_task ?? InitializeTaskAsPromise(); - } - - /// - /// 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. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private Task InitializeTaskAsPromise() - { - Debug.Assert(m_task == null); - return (m_task = new Task()); - } - - /// - /// 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. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private Task InitializeTaskAsStateMachineBox() - { - Debug.Assert(m_task == null); - return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ? - CreateDebugFinalizableAsyncStateMachineBox() : - new AsyncStateMachineBox()); - } - - /// - /// Completes the in the - /// RanToCompletion state with the specified result. - /// - /// The result to use to complete the task. - /// The task has already completed. - 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); - } - } - - /// Completes the already initialized task with the specified result. - /// The result to use to complete the task. - private void SetExistingTaskResult(TResult result) - { - Debug.Assert(m_task != null, "Expected non-null task"); - - if (AsyncCausalityTracer.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled) - { - LogExistingTaskCompletion(); - } - - if (!m_task.TrySetResult(result)) - { - ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); - } - } - - /// Handles logging for the successful completion of an operation. - private void LogExistingTaskCompletion() - { - Debug.Assert(m_task != null); - - if (AsyncCausalityTracer.LoggingOn) - { - AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, m_task.Id, AsyncCausalityStatus.Completed); - } - - // only log if we have a real task that was previously created - if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled) - { - System.Threading.Tasks.Task.RemoveFromActiveTasks(m_task.Id); - } - } - - /// - /// Completes the builder by using either the supplied completed task, or by completing - /// the builder's previously accessed task using default(TResult). - /// - /// A task already completed with the value default(TResult). - /// The task has already completed. - internal void SetResult(Task 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); - } - } - - /// - /// 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 task has already completed. - 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 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); - } - } - - /// - /// 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. - /// - /// - /// This should only be invoked from within an asynchronous method, - /// and only by the debugger. - /// - 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 (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. - } - - /// - /// 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 or other members that lazily initialize the task. - /// - internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox(); - - /// - /// Gets a task for the specified result. This will either - /// be a cached or new task, never null. - /// - /// The result for which we need a task. - /// The completed task containing the result. - [MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm - internal static Task GetTaskForResult(TResult result) - { - // 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 task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.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). - 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 task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN]; - return Unsafe.As>(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; - } - - // No cached task is available. Manufacture a new one for this result. - 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 readonly static Task TrueTask = CreateCacheableTask(true); - /// A cached Task{Boolean}.Result == false. - internal readonly static Task FalseTask = CreateCacheableTask(false); - - /// The cache of Task{Int32}. - internal readonly static 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(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); - } - } - } - - /// Gets whether we should be tracking async method completions for eventing. - internal static bool TrackAsyncMethodCompletion - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => TplEtwProvider.Log.IsEnabled(EventLevel.Warning, TplEtwProvider.Keywords.AsyncMethod); - } - - /// 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; - - 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; - - /// Throws the exception on the ThreadPool. - /// The exception to propagate. - /// The target context on which to propagate the exception. Null to use the ThreadPool. - 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 we have the new error reporting APIs, report this error. Otherwise, Propagate the exception(s) on the ThreadPool -#if FEATURE_COMINTEROP - if (!WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException)) -#endif // FEATURE_COMINTEROP - { - ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi); - } - } - - /// - /// 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 - { - 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) - 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 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); - } - } -} -- cgit v1.2.3