网上演练
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

#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