You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
12 KiB
303 lines
12 KiB
5 years ago
|
#if NET20 || NET30 || NET35 || !NET_4_6
|
||
|
|
||
|
using System.Diagnostics.Contracts;
|
||
|
using System.Runtime.ExceptionServices;
|
||
|
using System.Security;
|
||
|
|
||
|
namespace System.Threading.Tasks
|
||
|
{
|
||
|
/// <summary>Base task continuation class used for await continuations.</summary>
|
||
|
internal class AwaitTaskContinuation : TaskContinuation, IThreadPoolWorkItem
|
||
|
{
|
||
|
/// <summary>The ExecutionContext with which to run the continuation.</summary>
|
||
|
private ExecutionContext _capturedContext;
|
||
|
|
||
|
/// <summary>The action to invoke.</summary>
|
||
|
protected readonly Action Action;
|
||
|
|
||
|
/// <summary>Initializes the continuation.</summary>
|
||
|
/// <param name="action">The action to invoke. Must not be null.</param>
|
||
|
/// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param>
|
||
|
[SecurityCritical]
|
||
|
internal AwaitTaskContinuation(Action action, bool flowExecutionContext)
|
||
|
{
|
||
|
Contract.Requires(action != null);
|
||
|
Action = action;
|
||
|
if (flowExecutionContext)
|
||
|
{
|
||
|
_capturedContext = ExecutionContext.Capture();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Creates a task to run the action with the specified state on the specified scheduler.</summary>
|
||
|
/// <param name="action">The action to run. Must not be null.</param>
|
||
|
/// <param name="state">The state to pass to the action. Must not be null.</param>
|
||
|
/// <param name="scheduler">The scheduler to target.</param>
|
||
|
/// <returns>The created task.</returns>
|
||
|
protected Task CreateTask(Action<object> 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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets whether the current thread is an appropriate location to inline a continuation's execution.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// 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.
|
||
|
/// </remarks>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>IThreadPoolWorkItem override, which is the entry function for this when the ThreadPool scheduler decides to run it.</summary>
|
||
|
[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 */
|
||
|
}
|
||
|
|
||
|
/// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary>
|
||
|
[SecurityCritical]
|
||
|
private static ContextCallback _invokeActionCallback;
|
||
|
|
||
|
/// <summary>Runs an action provided as an object parameter.</summary>
|
||
|
/// <param name="state">The Action to invoke.</param>
|
||
|
[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;
|
||
|
}
|
||
|
|
||
|
/// <summary>Runs the callback synchronously with the provided state.</summary>
|
||
|
/// <param name="callback">The callback to run.</param>
|
||
|
/// <param name="state">The state to pass to the callback.</param>
|
||
|
/// <param name="currentTask">A reference to Task.t_currentTask.</param>
|
||
|
[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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Invokes or schedules the action to be executed.</summary>
|
||
|
/// <param name="action">The action to invoke or queue.</param>
|
||
|
/// <param name="allowInlining">
|
||
|
/// true to allow inlining, or false to force the action to run asynchronously.
|
||
|
/// </param>
|
||
|
/// <param name="currentTask">
|
||
|
/// 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.
|
||
|
/// </param>
|
||
|
/// <remarks>
|
||
|
/// 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.
|
||
|
/// </remarks>
|
||
|
[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);
|
||
|
}
|
||
|
|
||
|
/// <summary>Throws the exception asynchronously on the ThreadPool.</summary>
|
||
|
/// <param name="exc">The exception to throw.</param>
|
||
|
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
|