#if NET20 || NET30 || NET35 || !NET_4_6 using System.Diagnostics.Contracts; using System.Runtime.ExceptionServices; using System.Security; namespace System.Threading.Tasks { /// Base task continuation class used for await continuations. internal class AwaitTaskContinuation : TaskContinuation, IThreadPoolWorkItem { /// The ExecutionContext with which to run the continuation. private ExecutionContext _capturedContext; /// The action to invoke. protected readonly Action Action; /// Initializes the continuation. /// The action to invoke. Must not be null. /// Whether to capture and restore ExecutionContext. [SecurityCritical] internal AwaitTaskContinuation(Action action, bool flowExecutionContext) { Contract.Requires(action != null); Action = action; if (flowExecutionContext) { _capturedContext = ExecutionContext.Capture(); } } /// Creates a task to run the action with the specified state on the specified scheduler. /// The action to run. Must not be null. /// The state to pass to the action. Must not be null. /// The scheduler to target. /// The created task. protected Task CreateTask(Action action, object state, TaskScheduler scheduler) { Contract.Requires(action != null); Contract.Requires(scheduler != null); return new Task( action, state, null, default(CancellationToken), TaskCreationOptions.None, InternalTaskOptions.QueuedByRuntime, scheduler) { CapturedContext = _capturedContext }; } [SecuritySafeCritical] internal override void Run(Task completedTask, bool canInlineContinuationTask) { // For the base AwaitTaskContinuation, we allow inlining if our caller allows it // and if we're in a "valid location" for it. See the comments on // IsValidLocationForInlining for more about what's valid. For performance // reasons we would like to always inline, but we don't in some cases to avoid // running arbitrary amounts of work in suspected "bad locations", like UI threads. if (canInlineContinuationTask && IsValidLocationForInlining) { RunCallback(GetInvokeActionCallback(), Action, ref Task.InternalCurrent); // any exceptions from Action will be handled by s_callbackRunAction } else { // We couldn't inline, so now we need to schedule it ThreadPoolAdapter.QueueWorkItem(this); } } /// /// Gets whether the current thread is an appropriate location to inline a continuation's execution. /// /// /// Returns whether SynchronizationContext is null and we're in the default scheduler. /// If the await had a SynchronizationContext/TaskScheduler where it began and the /// default/ConfigureAwait(true) was used, then we won't be on this path. If, however, /// ConfigureAwait(false) was used, or the SynchronizationContext and TaskScheduler were /// naturally null/Default, then we might end up here. If we do, we need to make sure /// that we don't execute continuations in a place that isn't set up to handle them, e.g. /// running arbitrary amounts of code on the UI thread. It would be "correct", but very /// expensive, to always run the continuations asynchronously, incurring lots of context /// switches and allocations and locks and the like. As such, we employ the heuristic /// that if the current thread has a non-null SynchronizationContext or a non-default /// scheduler, then we better not run arbitrary continuations here. /// internal static bool IsValidLocationForInlining { get { if (SynchronizationContext.Current == null) { return false; } var scheduler = TaskScheduler.Current; if (scheduler != null && scheduler != TaskScheduler.Default) { return false; } return true; } } /// IThreadPoolWorkItem override, which is the entry function for this when the ThreadPool scheduler decides to run it. [SecurityCritical] private void ExecuteWorkItemHelper() { // We're not inside of a task, so t_currentTask doesn't need to be specially maintained. // We're on a thread pool thread with no higher-level callers, so exceptions can just propagate. // If there's no execution context, just invoke the delegate. if (_capturedContext == null) { Action.Invoke(); } // If there is an execution context, get the cached delegate and run the action under the context. else { try { ExecutionContext.Run(_capturedContext, GetInvokeActionCallback(), Action); } finally { _capturedContext = null; } } } [SecurityCritical] void IThreadPoolWorkItem.ExecuteWorkItem() { // inline the fast path if (_capturedContext == null) { Action.Invoke(); } else { ExecuteWorkItemHelper(); } } [SecurityCritical] void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ } /// Cached delegate that invokes an Action passed as an object parameter. [SecurityCritical] private static ContextCallback _invokeActionCallback; /// Runs an action provided as an object parameter. /// The Action to invoke. [SecurityCritical] private static void InvokeAction(object state) { ((Action)state)(); } [SecurityCritical] protected static ContextCallback GetInvokeActionCallback() { var callback = _invokeActionCallback; if (callback == null) { _invokeActionCallback = callback = InvokeAction; } // lazily initialize SecurityCritical delegate return callback; } /// Runs the callback synchronously with the provided state. /// The callback to run. /// The state to pass to the callback. /// A reference to Task.t_currentTask. [SecurityCritical] protected void RunCallback(ContextCallback callback, object state, ref Task currentTask) { if (callback == null) { Contract.Requires(false); throw new ArgumentNullException("callback"); } Contract.Assert(currentTask == Task.InternalCurrent); // Pretend there's no current task, so that no task is seen as a parent // and TaskScheduler.Current does not reflect false information var prevCurrentTask = currentTask; try { if (prevCurrentTask != null) { currentTask = null; } if (_capturedContext == null) { // If there's no captured context, just run the callback directly. callback(state); } else { // Otherwise, use the captured context to do so. ExecutionContext.Run(_capturedContext, callback, state); } } catch (Exception exc) { // we explicitly do not request handling of dangerous exceptions like AVs ThrowAsyncIfNecessary(exc); } finally { // Restore the current task information if (prevCurrentTask != null) { currentTask = prevCurrentTask; } _capturedContext = null; } } /// Invokes or schedules the action to be executed. /// The action to invoke or queue. /// /// true to allow inlining, or false to force the action to run asynchronously. /// /// /// A reference to the t_currentTask thread static value. /// This is passed by-ref rather than accessed in the method in order to avoid /// unnecessary thread-static writes. /// /// /// No ExecutionContext work is performed used. This method is only used in the /// case where a raw Action continuation delegate was stored into the Task, which /// only happens in Task.SetContinuationForAwait if execution context flow was disabled /// via using TaskAwaiter.UnsafeOnCompleted or a similar path. /// [SecurityCritical] internal static void RunOrScheduleAction(Action action, bool allowInlining, ref Task currentTask) { // NOTICE this method has no null check Contract.Assert(currentTask == Task.InternalCurrent); // If we're not allowed to run here, schedule the action if (!allowInlining || !IsValidLocationForInlining) { UnsafeScheduleAction(action); return; } // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution var prevCurrentTask = currentTask; try { if (prevCurrentTask != null) { currentTask = null; } action(); } catch (Exception exception) { ThrowAsyncIfNecessary(exception); } finally { if (prevCurrentTask != null) { currentTask = prevCurrentTask; } } } [SecurityCritical] internal static void UnsafeScheduleAction(Action action) { var atc = new AwaitTaskContinuation(action, /*flowExecutionContext:*/ false); ThreadPoolAdapter.QueueWorkItem(atc); } /// Throws the exception asynchronously on the ThreadPool. /// The exception to throw. protected static void ThrowAsyncIfNecessary(Exception exc) { // Awaits should never experience an exception (other than an TAE or ADUE), // unless a malicious user is explicitly passing a throwing action into the TaskAwaiter. // We don't want to allow the exception to propagate on this stack, as it'll emerge in random places, // and we can't fail fast, as that would allow for elevation of privilege. // // If unhandled error reporting APIs are available use those, otherwise since this // would have executed on the thread pool otherwise, let it propagate there. if (exc is ThreadAbortException || exc is AppDomainUnloadedException) { return; } var edi = ExceptionDispatchInfo.Capture(exc); ThreadPool.QueueUserWorkItem(s => ((ExceptionDispatchInfo)s).Throw(), edi); } } } #endif