// 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.ExceptionServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Threading.Tasks;
#if FEATURE_COMINTEROP
using System.Runtime.InteropServices.WindowsRuntime;
#endif // FEATURE_COMINTEROP
namespace System.Runtime.CompilerServices
{
///
/// Provides a builder for asynchronous methods that return void.
/// This type is intended for compiler use only.
///
[HostProtection(Synchronization = true, ExternalThreading = true)]
public struct AsyncVoidMethodBuilder
{
/// The synchronization context associated with this operation.
private SynchronizationContext m_synchronizationContext;
/// State related to the IAsyncStateMachine.
private AsyncMethodBuilderCore m_coreState; // mutable struct: must not be readonly
/// Task used for debugging and logging purposes only. Lazily initialized.
private Task m_task;
/// Initializes a new .
/// The initialized .
public static AsyncVoidMethodBuilder Create()
{
// Capture the current sync context. If there isn't one, use the dummy s_noContextCaptured
// instance; this allows us to tell the state of no captured context apart from the state
// of an improperly constructed builder instance.
SynchronizationContext sc = SynchronizationContext.CurrentNoFlow;
if (sc != null)
sc.OperationStarted();
return new AsyncVoidMethodBuilder() { m_synchronizationContext = sc };
}
/// 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).
[SecuritySafeCritical]
[DebuggerStepThrough]
public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
// See comment on AsyncMethodBuilderCore.Start
// AsyncMethodBuilderCore.Start(ref stateMachine);
if (stateMachine == null) throw new ArgumentNullException("stateMachine");
Contract.EndContractBlock();
// Run the MoveNext method within a copy-on-write ExecutionContext scope.
// This allows us to undo any ExecutionContext changes made in MoveNext,
// so that they won't "leak" out of the first await.
Thread currentThread = Thread.CurrentThread;
ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
stateMachine.MoveNext();
}
finally
{
ecs.Undo(currentThread);
}
}
/// Associates the builder with the state machine it represents.
/// The heap-allocated state machine object.
/// The argument was null (Nothing in Visual Basic).
/// The builder is incorrectly initialized.
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
m_coreState.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore
}
///
/// 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
{
AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
// If this is our first await, such that we've not yet boxed the state machine, do so now.
if (m_coreState.m_stateMachine == null)
{
if (AsyncCausalityTracer.LoggingOn)
AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0);
// Box the state machine, then tell the boxed instance to call back into its own builder,
// so we can cache the boxed reference. NOTE: The language compiler may choose to use
// a class instead of a struct for the state machine for debugging purposes; in such cases,
// the stateMachine will already be an object.
m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null);
}
awaiter.OnCompleted(continuation);
}
catch (Exception exc)
{
// Prevent exceptions from leaking to the call site, which could
// then allow multiple flows of execution through the same async method
// if the awaiter had already scheduled the continuation by the time
// the exception was thrown. We propagate the exception on the
// ThreadPool because we can trust it to not throw, unlike
// if we were to go to a user-supplied SynchronizationContext,
// whose Post method could easily throw.
AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
}
}
///
/// 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.
[SecuritySafeCritical]
public void AwaitUnsafeOnCompleted(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine
{
try
{
AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
// If this is our first await, such that we've not yet boxed the state machine, do so now.
if (m_coreState.m_stateMachine == null)
{
if (AsyncCausalityTracer.LoggingOn)
AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0);
// Box the state machine, then tell the boxed instance to call back into its own builder,
// so we can cache the boxed reference. NOTE: The language compiler may choose to use
// a class instead of a struct for the state machine for debugging purposes; in such cases,
// the stateMachine will already be an object.
m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null);
}
awaiter.UnsafeOnCompleted(continuation);
}
catch (Exception e)
{
AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
}
}
/// Completes the method builder successfully.
public void SetResult()
{
if (AsyncCausalityTracer.LoggingOn)
AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Completed);
if (m_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) throw new ArgumentNullException("exception");
Contract.EndContractBlock();
if (AsyncCausalityTracer.LoggingOn)
AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Error);
if (m_synchronizationContext != null)
{
// If we captured a synchronization context, Post the throwing of the exception to it
// and decrement its outstanding operation count.
try
{
AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: m_synchronizationContext);
}
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);
}
}
/// Notifies the current synchronization context that the operation completed.
private void NotifySynchronizationContextOfCompletion()
{
Contract.Assert(m_synchronizationContext != null, "Must only be used with a non-null context.");
try
{
m_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);
}
}
// This property lazily instantiates the Task in a non-thread-safe manner.
private Task Task
{
get
{
if (m_task == null) m_task = new Task();
return m_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.
///
private object ObjectIdForDebugger { get { return this.Task; } }
}
///
/// 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.
///
[HostProtection(Synchronization = true, ExternalThreading = true)]
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
/// Initializes a new .
/// The initialized .
public static AsyncTaskMethodBuilder Create()
{
return default(AsyncTaskMethodBuilder);
// Note: If ATMB.Create is modified to do any initialization, this
// method needs to be updated to do m_builder = ATMB.Create().
}
/// Initiates the builder's execution with the associated state machine.
/// Specifies the type of the state machine.
/// The state machine instance, passed by reference.
[SecuritySafeCritical]
[DebuggerStepThrough]
public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
// See comment on AsyncMethodBuilderCore.Start
// AsyncMethodBuilderCore.Start(ref stateMachine);
if (stateMachine == null) throw new ArgumentNullException("stateMachine");
Contract.EndContractBlock();
// Run the MoveNext method within a copy-on-write ExecutionContext scope.
// This allows us to undo any ExecutionContext changes made in MoveNext,
// so that they won't "leak" out of the first await.
Thread currentThread = Thread.CurrentThread;
ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
stateMachine.MoveNext();
}
finally
{
ecs.Undo(currentThread);
}
}
/// Associates the builder with the state machine it represents.
/// The heap-allocated state machine object.
/// The argument was null (Nothing in Visual Basic).
/// The builder is incorrectly initialized.
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
m_builder.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore
}
///
/// 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 { get { return m_builder.Task; } }
///
/// Completes the in the
/// RanToCompletion state.
///
/// The builder is not initialized.
/// The task has already completed.
public void SetResult()
{
// Accessing AsyncTaskMethodBuilder.s_cachedCompleted is faster than
// accessing AsyncTaskMethodBuilder.s_defaultResultTask.
m_builder.SetResult(s_cachedCompleted);
}
///
/// 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 pruposes, and only in a single-threaded manner
/// when no other threads are in the middle of accessing this property or this.Task.
///
private object ObjectIdForDebugger { get { return this.Task; } }
}
///
/// 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.
///
[HostProtection(Synchronization = true, ExternalThreading = true)]
public struct AsyncTaskMethodBuilder
{
/// A cached task for default(TResult).
internal readonly static Task s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
// WARNING: For performance reasons, the m_task field is lazily initialized.
// For correct results, the struct AsyncTaskMethodBuilder must
// always be used from the same location/copy, at least until m_task is
// initialized. If that guarantee is broken, the field could end up being
// initialized on the wrong copy.
/// State related to the IAsyncStateMachine.
private AsyncMethodBuilderCore m_coreState; // mutable struct: must not be readonly
/// The lazily-initialized built task.
private Task m_task; // lazily-initialized: must not be readonly
/// Initializes a new .
/// The initialized .
public static AsyncTaskMethodBuilder Create()
{
return default(AsyncTaskMethodBuilder);
// NOTE: If this method is ever updated to perform more initialization,
// ATMB.Create must also be updated to call this Create method.
}
/// Initiates the builder's execution with the associated state machine.
/// Specifies the type of the state machine.
/// The state machine instance, passed by reference.
[SecuritySafeCritical]
[DebuggerStepThrough]
public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
// See comment on AsyncMethodBuilderCore.Start
// AsyncMethodBuilderCore.Start(ref stateMachine);
if (stateMachine == null) throw new ArgumentNullException("stateMachine");
Contract.EndContractBlock();
// Run the MoveNext method within a copy-on-write ExecutionContext scope.
// This allows us to undo any ExecutionContext changes made in MoveNext,
// so that they won't "leak" out of the first await.
Thread currentThread = Thread.CurrentThread;
ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
stateMachine.MoveNext();
}
finally
{
ecs.Undo(currentThread);
}
}
/// Associates the builder with the state machine it represents.
/// The heap-allocated state machine object.
/// The argument was null (Nothing in Visual Basic).
/// The builder is incorrectly initialized.
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
m_coreState.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore
}
///
/// 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
{
AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
// If this is our first await, such that we've not yet boxed the state machine, do so now.
if (m_coreState.m_stateMachine == null)
{
// Force the Task to be initialized prior to the first suspending await so
// that the original stack-based builder has a reference to the right Task.
var builtTask = this.Task;
// Box the state machine, then tell the boxed instance to call back into its own builder,
// so we can cache the boxed reference. NOTE: The language compiler may choose to use
// a class instead of a struct for the state machine for debugging purposes; in such cases,
// the stateMachine will already be an object.
m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, builtTask);
}
awaiter.OnCompleted(continuation);
}
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.
[SecuritySafeCritical]
public void AwaitUnsafeOnCompleted(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine
{
try
{
AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
// If this is our first await, such that we've not yet boxed the state machine, do so now.
if (m_coreState.m_stateMachine == null)
{
// Force the Task to be initialized prior to the first suspending await so
// that the original stack-based builder has a reference to the right Task.
var builtTask = this.Task;
// Box the state machine, then tell the boxed instance to call back into its own builder,
// so we can cache the boxed reference. NOTE: The language compiler may choose to use
// a class instead of a struct for the state machine for debugging purposes; in such cases,
// the stateMachine will already be an object.
m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, builtTask);
}
awaiter.UnsafeOnCompleted(continuation);
}
catch (Exception e)
{
AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
}
}
/// Gets the for this builder.
/// The representing the builder's asynchronous operation.
public Task Task
{
get
{
// Get and return the task. If there isn't one, first create one and store it.
var task = m_task;
if (task == null) { m_task = task = new Task(); }
return task;
}
}
///
/// 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.
var task = m_task;
if (task == null)
{
m_task = GetTaskForResult(result);
Contract.Assert(m_task != null, "GetTaskForResult should never return null");
}
// Slow path: complete the existing task.
else
{
if (AsyncCausalityTracer.LoggingOn)
AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, 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(task.Id);
}
if (!task.TrySetResult(result))
{
throw new InvalidOperationException(Environment.GetResourceString("TaskT_TransitionToFinal_AlreadyCompleted"));
}
}
}
///
/// 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)
{
Contract.Requires(completedTask != null, "Expected non-null task");
Contract.Requires(completedTask.Status == TaskStatus.RanToCompletion, "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.
var task = m_task;
if (task == null)
{
m_task = completedTask;
}
else
{
// Otherwise, complete the task that's there.
SetResult(default(TResult));
}
}
///
/// 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) throw new ArgumentNullException("exception");
Contract.EndContractBlock();
var task = m_task;
if (task == null)
{
// Get the task, forcing initialization if it hasn't already been initialized.
task = this.Task;
}
// If the exception represents cancellation, cancel the task. Otherwise, fault the task.
var oce = exception as OperationCanceledException;
bool successfullySet = oce != null ?
task.TrySetCanceled(oce.CancellationToken, oce) :
task.TrySetException(exception);
// Unlike with TaskCompletionSource, we do not need to spin here until m_task 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)
{
throw new InvalidOperationException(Environment.GetResourceString("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
this.Task.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.
///
private object ObjectIdForDebugger { get { return this.Task; } }
///
/// 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.
[SecuritySafeCritical] // for JitHelpers.UnsafeCast
private Task GetTaskForResult(TResult result)
{
Contract.Ensures(
EqualityComparer.Default.Equals(result, Contract.Result>().Result),
"The returned task's Result must return the same value as the specified result value.");
// 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
// - Decimal
// - 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(Boolean)) // only the relevant branches are kept for each value-type generic instantiation
{
Boolean value = (Boolean)(object)result;
Task task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask;
return JitHelpers.UnsafeCast>(task); // UnsafeCast avoids type check we know will succeed
}
// For Int32, we cache a range of common values, e.g. [-1,4).
else if (typeof(TResult) == typeof(Int32))
{
// 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.
Int32 value = (Int32)(object)result;
if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
{
Task task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
return JitHelpers.UnsafeCast>(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(UInt32) && default(UInt32) == (UInt32)(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(Decimal) && default(Decimal) == (Decimal)(object)result) ||
(typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) ||
(typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) ||
(typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) ||
(typeof(TResult) == typeof(UInt16) && default(UInt16) == (UInt16)(object)result) ||
(typeof(TResult) == typeof(IntPtr) && default(IntPtr) == (IntPtr)(object)result) ||
(typeof(TResult) == typeof(UIntPtr) && default(UIntPtr) == (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 Int32 INCLUSIVE_INT32_MIN = -1;
/// The maximum value, exclusive, for which we want a cached task.
internal const Int32 EXCLUSIVE_INT32_MAX = 9;
/// Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).
private static Task[] CreateInt32Tasks()
{
Contract.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)
{
return new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken));
}
}
/// Holds state related to the builder's IAsyncStateMachine.
/// This is a mutable struct. Be very delicate with it.
internal struct AsyncMethodBuilderCore
{
/// A reference to the heap-allocated state machine object associated with this builder.
internal IAsyncStateMachine m_stateMachine;
/// A cached Action delegate used when dealing with a default ExecutionContext.
internal Action m_defaultContextAction;
// This method is copy&pasted into the public Start methods to avoid size overhead of valuetype generic instantiations.
// Ideally, we would build intrinsics to get the raw ref address and raw code address of MoveNext, and just use the shared implementation.
#if false
/// 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 is null (Nothing in Visual Basic).
[SecuritySafeCritical]
[DebuggerStepThrough]
internal static void Start(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null) throw new ArgumentNullException("stateMachine");
Contract.EndContractBlock();
// Run the MoveNext method within a copy-on-write ExecutionContext scope.
// This allows us to undo any ExecutionContext changes made in MoveNext,
// so that they won't "leak" out of the first await.
Thread currentThread = Thread.CurrentThread;
ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ExecutionContext.EstablishCopyOnWriteScope(currentThread, ref ecs);
stateMachine.MoveNext();
}
finally
{
ecs.Undo(currentThread);
}
}
#endif
/// 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) throw new ArgumentNullException("stateMachine");
Contract.EndContractBlock();
if (m_stateMachine != null) throw new InvalidOperationException(Environment.GetResourceString("AsyncMethodBuilder_InstanceNotInitialized"));
m_stateMachine = stateMachine;
}
///
/// Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method.
/// On first invocation, the supplied state machine will be boxed.
///
/// Specifies the type of the method builder used.
/// Specifies the type of the state machine used.
/// The builder.
/// The state machine.
/// An Action to provide to the awaiter.
[SecuritySafeCritical]
internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize)
{
Contract.Assert(m_defaultContextAction == null || m_stateMachine != null,
"Expected non-null m_stateMachine on non-null m_defaultContextAction");
// Alert a listening debugger that we can't make forward progress unless it slips threads.
// If we don't do this, and a method that uses "await foo;" is invoked through funceval,
// we could end up hooking up a callback to push forward the async method's state machine,
// the debugger would then abort the funceval after it takes too long, and then continuing
// execution could result in another callback being hooked up. At that point we have
// multiple callbacks registered to push the state machine, which could result in bad behavior.
Debugger.NotifyOfCrossThreadDependency();
// The builder needs to flow ExecutionContext, so capture it.
var capturedContext = ExecutionContext.FastCapture(); // ok to use FastCapture as we haven't made any permission demands/asserts
// If the ExecutionContext is the default context, try to use a cached delegate, creating one if necessary.
Action action;
MoveNextRunner runner;
if (capturedContext != null && capturedContext.IsPreAllocatedDefault)
{
// Get the cached delegate, and if it's non-null, return it.
action = m_defaultContextAction;
if (action != null)
{
Contract.Assert(m_stateMachine != null, "If the delegate was set, the state machine should have been as well.");
return action;
}
// There wasn't a cached delegate, so create one and cache it.
// The delegate won't be usable until we set the MoveNextRunner's target state machine.
runner = new MoveNextRunner(m_stateMachine);
action = new Action(runner.RunWithDefaultContext);
if (taskForTracing != null)
{
action = OutputAsyncCausalityEvents(taskForTracing, action);
}
m_defaultContextAction = action;
}
// Otherwise, create an Action that flows this context. The context may be null.
// The delegate won't be usable until we set the MoveNextRunner's target state machine.
else
{
var runnerWithContext = new MoveNextRunnerWithContext(capturedContext, m_stateMachine);
runner = runnerWithContext;
action = new Action(runnerWithContext.RunWithCapturedContext);
if (taskForTracing != null)
{
action = OutputAsyncCausalityEvents(taskForTracing, action);
}
// NOTE: If capturedContext is null, we could create the Action to point directly
// to m_stateMachine.MoveNext. However, that follows a much more expensive
// delegate creation path.
}
if (m_stateMachine == null)
runnerToInitialize = runner;
return action;
}
private Action OutputAsyncCausalityEvents(Task innerTask, Action continuation)
{
return CreateContinuationWrapper(continuation, () =>
{
AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, innerTask.Id, CausalitySynchronousWork.Execution);
// Invoke the original continuation
continuation.Invoke();
AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution);
}, innerTask);
}
internal void PostBoxInitialization(IAsyncStateMachine stateMachine, MoveNextRunner runner, Task builtTask)
{
if (builtTask != null)
{
if (AsyncCausalityTracer.LoggingOn)
AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, builtTask.Id, "Async: " + stateMachine.GetType().Name, 0);
if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
System.Threading.Tasks.Task.AddToActiveTasks(builtTask);
}
m_stateMachine = stateMachine;
m_stateMachine.SetStateMachine(m_stateMachine);
Contract.Assert(runner.m_stateMachine == null, "The runner's state machine should not yet have been populated.");
Contract.Assert(m_stateMachine != null, "The builder's state machine field should have been initialized.");
// Now that we have the state machine, store it into the runner that the action delegate points to.
// And return the action.
runner.m_stateMachine = m_stateMachine; // only after this line is the Action delegate usable
}
/// 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);
}
}
/// Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext.
internal sealed class MoveNextRunnerWithContext : MoveNextRunner
{
/// The context with which to run MoveNext.
private readonly ExecutionContext m_context;
/// Initializes the runner.
/// The context with which to run MoveNext.
[SecurityCritical] // Run needs to be SSC to map to Action delegate, so to prevent misuse, we only allow construction through SC
internal MoveNextRunnerWithContext(ExecutionContext context, IAsyncStateMachine stateMachine) : base(stateMachine)
{
m_context = context;
}
/// Invokes MoveNext under the provided context.
[SecuritySafeCritical]
internal void RunWithCapturedContext()
{
Contract.Assert(m_stateMachine != null, "The state machine must have been set before calling Run.");
if (m_context != null)
{
try
{
// Use the context and callback to invoke m_stateMachine.MoveNext.
ExecutionContext.Run(m_context, InvokeMoveNextCallback, m_stateMachine, preserveSyncCtx: true);
}
finally { m_context.Dispose(); }
}
else
{
m_stateMachine.MoveNext();
}
}
}
/// Provides the ability to invoke a state machine's MoveNext method.
internal class MoveNextRunner
{
/// The state machine whose MoveNext method should be invoked.
internal IAsyncStateMachine m_stateMachine;
/// Initializes the runner.
[SecurityCritical] // Run needs to be SSC to map to Action delegate, so to prevent misuse, we only allow construction through SC
internal MoveNextRunner(IAsyncStateMachine stateMachine)
{
m_stateMachine = stateMachine;
}
/// Invokes MoveNext under the default context.
[SecuritySafeCritical]
internal void RunWithDefaultContext()
{
Contract.Assert(m_stateMachine != null, "The state machine must have been set before calling Run.");
ExecutionContext.Run(ExecutionContext.PreAllocatedDefault, InvokeMoveNextCallback, m_stateMachine, preserveSyncCtx: true);
}
/// Gets a delegate to the InvokeMoveNext method.
protected static ContextCallback InvokeMoveNextCallback
{
[SecuritySafeCritical]
get { return s_invokeMoveNext ?? (s_invokeMoveNext = InvokeMoveNext); }
}
/// Cached delegate used with ExecutionContext.Run.
[SecurityCritical]
private static ContextCallback s_invokeMoveNext; // lazily-initialized due to SecurityCritical attribution
/// Invokes the MoveNext method on the supplied IAsyncStateMachine.
/// The IAsyncStateMachine machine instance.
[SecurityCritical] // necessary for ContextCallback in CoreCLR
private static void InvokeMoveNext(object stateMachine)
{
((IAsyncStateMachine)stateMachine).MoveNext();
}
}
///
/// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
/// However debuggers and profilers need more information about what that action is. (In particular what
/// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
/// which when invoked just does the original action (the invoke action), but also remembers other information
/// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
// We also store that task if the action is associate with at task.
///
private class ContinuationWrapper
{
internal readonly Action m_continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
private readonly Action m_invokeAction; // This wrapper is an action that wraps another action, this is that Action.
internal readonly Task m_innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
internal ContinuationWrapper(Action continuation, Action invokeAction, Task innerTask)
{
Contract.Requires(continuation != null, "Expected non-null continuation");
// If we don't have a task, see if our continuation is a wrapper and use that.
if (innerTask == null)
innerTask = TryGetContinuationTask(continuation);
m_continuation = continuation;
m_innerTask = innerTask;
m_invokeAction = invokeAction;
}
internal void Invoke()
{
m_invokeAction();
}
}
internal static Action CreateContinuationWrapper(Action continuation, Action invokeAction, Task innerTask = null)
{
return new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
}
internal static Action TryGetStateMachineForDebugger(Action action)
{
object target = action.Target;
var runner = target as AsyncMethodBuilderCore.MoveNextRunner;
if (runner != null)
{
return new Action(runner.m_stateMachine.MoveNext);
}
var continuationWrapper = target as ContinuationWrapper;
if (continuationWrapper != null)
{
return TryGetStateMachineForDebugger(continuationWrapper.m_continuation);
}
return action;
}
///
/// Given an action, see if it is a contiunation wrapper and has a Task associated with it. If so return it (null otherwise)
///
internal static Task TryGetContinuationTask(Action action)
{
if (action != null)
{
var asWrapper = action.Target as ContinuationWrapper;
if (asWrapper != null)
return asWrapper.m_innerTask;
}
return null;
}
}
}