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

691 lines
31 KiB

#if NET20 || NET30 || NET35 || !NET_4_6
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Security;
using LinqInternal.Core;
namespace System.Threading.Tasks
{
public partial class Task
{
private static readonly Predicate<Task> _isExceptionObservedByParentPredicate = (t => t.IsExceptionObservedByParent);
private static readonly Action<object> _taskCancelCallback = (TaskCancelCallback);
private int _cancellationAcknowledged;
private StrongBox<CancellationTokenRegistration> _cancellationRegistration;
private int _cancellationRequested;
private int _completionCountdown = 1;
private List<Task> _exceptionalChildren;
private int _exceptionObservedByParent;
private TaskExceptionHolder _exceptionsHolder;
private int _threadAbortedmanaged;
/// <summary>
/// The property formerly known as IsFaulted.
/// </summary>
internal bool ExceptionRecorded
{
get
{
var exceptionsHolder = Volatile.Read(ref _exceptionsHolder);
return exceptionsHolder != null && exceptionsHolder.ContainsFaultList;
}
}
internal bool IsCancellationAcknowledged
{
get { return Thread.VolatileRead(ref _cancellationAcknowledged) == 1; }
}
internal bool IsCancellationRequested
{
get { return Thread.VolatileRead(ref _cancellationRequested) == 1; }
}
internal bool IsChildReplica
{
get { return (_internalOptions & InternalTaskOptions.ChildReplica) != 0; }
}
/// <summary>
/// Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set,
/// This will only be used by the implicit wait to prevent double throws
/// </summary>
internal bool IsExceptionObservedByParent
{
get { return Thread.VolatileRead(ref _exceptionObservedByParent) == 1; }
}
internal bool IsSelfReplicatingRoot
{
get
{
// Return true if self-replicating bit is set and child replica bit is not set
return (_internalOptions & (InternalTaskOptions.SelfReplicating | InternalTaskOptions.ChildReplica)) == InternalTaskOptions.SelfReplicating;
}
}
/// <summary>
/// This is to be called just before the task does its final state transition.
/// It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list
/// </summary>
internal void AddExceptionsFromChildren()
{
// In rare occurences during AppDomainUnload() processing, it is possible for this method to be called
// simultaneously on the same task from two different contexts. This can result in m_exceptionalChildren
// being nulled out while it is being processed, which could lead to a NullReferenceException. To
// protect ourselves, we'll cache m_exceptionalChildren in a local variable.
var tmp = Volatile.Read(ref _exceptionalChildren);
if (tmp != null)
{
// This lock is necessary because even though AddExceptionsFromChildren is last to execute, it may still
// be racing with the code segment at the bottom of Finish() that prunes the exceptional child array.
lock (tmp)
{
foreach (var task in tmp)
{
// Ensure any exceptions thrown by children are added to the parent.
// In doing this, we are implicitly marking children as being "handled".
Contract.Assert(task.IsCompleted, "Expected all tasks in list to be completed");
if (task.IsFaulted && !task.IsExceptionObservedByParent)
{
var exceptionsHolder = Volatile.Read(ref task._exceptionsHolder);
if (exceptionsHolder == null)
{
Contract.Assert(false);
}
else
{
// No locking necessary since child task is finished adding exceptions
// and concurrent CreateExceptionObject() calls do not constitute
// a concurrency hazard.
AddException(exceptionsHolder.CreateExceptionObject(false, null));
}
}
}
}
// Reduce memory pressure by getting rid of the array
Volatile.Write(ref _exceptionalChildren, null);
}
}
/// <summary>
/// Checks if we registered a CT callback during construction, and deregisters it.
/// This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed
/// successfully or with an exception.
/// </summary>
internal void DeregisterCancellationCallback()
{
if (_cancellationRegistration != null)
{
// Harden against ODEs thrown from disposing of the CTR.
// Since the task has already been put into a final state by the time this
// is called, all we can do here is suppress the exception.
try
{
_cancellationRegistration.Value.Dispose();
}
catch (ObjectDisposedException exception)
{
GC.KeepAlive(exception);
// Empty
}
_cancellationRegistration = null;
}
}
// This is called in the case where a new child is added, but then encounters a CancellationToken-related exception.
// We need to subtract that child from m_completionCountdown, or the parent will never complete.
internal void DisregardChild()
{
Contract.Assert(InternalCurrent == this, "Task.DisregardChild(): Called from an external context");
Contract.Assert(Thread.VolatileRead(ref _completionCountdown) >= 2, "Task.DisregardChild(): Expected parent count to be >= 2");
Interlocked.Decrement(ref _completionCountdown);
}
internal void Finish(bool userDelegateExecuted)
{
if (!userDelegateExecuted)
{
// delegate didn't execute => no children. We can safely call the remaining finish stages
FinishStageTwo();
}
else
{
// Reaching this sub clause means there may be remaining active children,
// and we could be racing with one of them to call FinishStageTwo().
// So whoever does the final Interlocked.Dec is responsible to finish.
if ((_completionCountdown == 1 && !IsSelfReplicatingRoot) || Interlocked.Decrement(ref _completionCountdown) == 0)
{
FinishStageTwo();
}
else
{
// Apparently some children still remain. It will be up to the last one to process the completion of this task on their own thread.
// We will now yield the thread back to ThreadPool. Mark our state appropriately before getting out.
// We have to use an atomic update for this and make sure not to overwrite a final state,
// because at this very moment the last child's thread may be concurrently completing us.
// Otherwise we risk overwriting the TaskStatus which may have been set by that child task.
Interlocked.CompareExchange(ref _status, (int)TaskStatus.WaitingForChildrenToComplete, (int)TaskStatus.Running);
}
// Now is the time to prune exceptional children. We'll walk the list and removes the ones whose exceptions we might have observed after they threw.
// we use a local variable for exceptional children here because some other thread may be nulling out _exceptionalChildren
var exceptionalChildren = Volatile.Read(ref _exceptionalChildren);
if (exceptionalChildren != null)
{
lock (exceptionalChildren)
{
exceptionalChildren.RemoveAll(_isExceptionObservedByParentPredicate); // RemoveAll has better performance than doing it ourselves
}
}
}
}
// ASSUMES THAT A SUCCESSFUL CANCELLATION HAS JUST OCCURRED ON THIS TASK!!!
// And this method should be called at most once per task.
internal void FinishStageThree()
{
Action = null;
// Notify parent if this was an attached task
if (_parent != null && ((_parent._creationOptions & TaskCreationOptions.DenyChildAttach) == 0) && (_creationOptions & TaskCreationOptions.AttachedToParent) != 0)
{
_parent.ProcessChildCompletion(this);
}
// Activate continuations (if any).
FinishContinuations();
}
/// <summary>
/// FinishStageTwo is to be executed as soon as we known there are no more children to complete.
/// It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit)
/// ii) or on the thread that executed the last child.
/// </summary>
internal void FinishStageTwo()
{
AddExceptionsFromChildren();
// At this point, the task is done executing and waiting for its children,
// we can transition our task to a completion state.
TaskStatus completionState;
if (ExceptionRecorded)
{
completionState = TaskStatus.Faulted;
}
else if (IsCancellationRequested && IsCancellationAcknowledged)
{
// We transition into the TASK_STATE_CANCELED final state if the task's CT was signalled for cancellation,
// and the user delegate acknowledged the cancellation request by throwing an OCE,
// and the task hasn't otherwise transitioned into faulted state. (TASK_STATE_FAULTED trumps TASK_STATE_CANCELED)
//
// If the task threw an OCE without cancellation being requestsed (while the CT not being in signaled state),
// then we regard it as a regular exception
completionState = TaskStatus.Canceled;
}
else
{
completionState = TaskStatus.RanToCompletion;
}
// Use Interlocked.Exchange() to effect a memory fence, preventing
// any SetCompleted() (or later) instructions from sneak back before it.
Interlocked.Exchange(ref _status, (int)completionState);
// Set the completion event if it's been lazy allocated.
// And if we made a cancellation registration, it's now unnecessary.
MarkCompleted();
DeregisterCancellationCallback();
// ready to run continuations and notify parent.
FinishStageThree();
}
internal void FinishThreadAbortedTask(bool exceptionAdded, bool delegateRan)
{
if (Interlocked.CompareExchange(ref _threadAbortedmanaged, 1, 0) == 0)
{
var exceptionsHolder = Volatile.Read(ref _exceptionsHolder);
if (exceptionsHolder == null)
{
return;
}
if (exceptionAdded)
{
exceptionsHolder.MarkAsHandled(false);
}
Finish(delegateRan);
}
}
/// <summary>
/// The actual code which invokes the body of the task. This can be overriden in derived types.
/// </summary>
internal virtual void InnerInvoke()
{
// Invoke the delegate
Contract.Assert(Action != null, "Null action in InnerInvoke()");
var action = Action as Action;
if (action != null)
{
action();
return;
}
var actionWithState = Action as Action<object>;
if (actionWithState != null)
{
actionWithState(State);
return;
}
Contract.Assert(false, "Invalid Action in Task");
}
internal void ProcessChildCompletion(Task childTask)
{
Contract.Requires(childTask != null);
Contract.Requires(childTask.IsCompleted, "ProcessChildCompletion was called for an uncompleted task");
Contract.Assert(childTask._parent == this, "ProcessChildCompletion should only be called for a child of this task");
// if the child threw and we haven't observed it we need to save it for future reference
if (childTask.IsFaulted && !childTask.IsExceptionObservedByParent)
{
// Lazily initialize the child exception list
if (Volatile.Read(ref _exceptionalChildren) == null)
{
Interlocked.CompareExchange(ref _exceptionalChildren, new List<Task>(), null);
}
// In rare situations involving AppDomainUnload, it's possible (though unlikely) for FinishStageTwo() to be called
// multiple times for the same task. In that case, AddExceptionsFromChildren() could be nulling m_exceptionalChildren
// out at the same time that we're processing it, resulting in a NullReferenceException here. We'll protect
// ourselves by caching m_exceptionChildren in a local variable.
var tmp = Volatile.Read(ref _exceptionalChildren);
if (tmp != null)
{
lock (tmp)
{
tmp.Add(childTask);
}
}
}
if (Interlocked.Decrement(ref _completionCountdown) == 0)
{
// This call came from the final child to complete, and apparently we have previously given up this task's right to complete itself.
// So we need to invoke the final finish stage.
FinishStageTwo();
}
}
internal void RecordInternalCancellationRequest()
{
Thread.VolatileWrite(ref _cancellationRequested, 1);
}
internal void SetCancellationAcknowledged()
{
Thread.VolatileWrite(ref _cancellationAcknowledged, 1);
}
internal void ThrowIfExceptional(bool includeTaskCanceledExceptions)
{
Contract.Requires(IsCompleted, "ThrowIfExceptional(): Expected IsCompleted == true");
Exception exception = GetExceptions(includeTaskCanceledExceptions);
if (exception != null)
{
UpdateExceptionObservedStatus();
throw exception;
}
}
/// <summary>
/// Checks whether this is an attached task, and whether we are being called by the parent task.
/// And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that.
///
/// This is meant to be used internally when throwing an exception, and when WaitAll is gathering
/// exceptions for tasks it waited on. If this flag gets set, the implicit wait on children
/// will skip exceptions to prevent duplication.
///
/// This should only be called when this task has completed with an exception
///
/// </summary>
internal void UpdateExceptionObservedStatus()
{
if ((_parent != null) && ((_creationOptions & TaskCreationOptions.AttachedToParent) != 0) && ((_parent._creationOptions & TaskCreationOptions.DenyChildAttach) == 0) && InternalCurrent == _parent)
{
Thread.VolatileWrite(ref _exceptionObservedByParent, 1);
}
}
private static void ExecutionContextCallback(object obj)
{
var task = obj as Task;
if (task == null)
{
Contract.Assert(false, "expected a task object");
}
else
{
task.Execute();
}
}
private static void TaskCancelCallback(object obj)
{
var task = obj as Task;
if (task == null)
{
var tuple = obj as Tuple<Task, Task, TaskContinuation>;
if (tuple == null)
{
Contract.Assert(false, "task should have been non-null");
return;
}
task = tuple.Item1;
var antecedent = tuple.Item2;
var continuation = tuple.Item3;
antecedent.RemoveContinuation(continuation);
}
task.InternalCancel(false);
}
/// <summary>
/// Internal function that will be called by a new child task to add itself to
/// the children list of the parent (this).
///
/// Since a child task can only be created from the thread executing the action delegate
/// of this task, reentrancy is neither required nor supported. This should not be called from
/// anywhere other than the task construction/initialization codepaths.
/// </summary>
private void AddNewChild()
{
Contract.Assert(InternalCurrent == this || IsSelfReplicatingRoot, "Task.AddNewChild(): Called from an external context");
if (_completionCountdown == 1 && !IsSelfReplicatingRoot)
{
// A count of 1 indicates so far there was only the parent, and this is the first child task
// Single kid => no fuss about who else is accessing the count. Let's save ourselves 100 cycles
// We exclude self replicating root tasks from this optimization, because further child creation can take place on
// other cores and with bad enough timing this write may not be visible to them.
_completionCountdown++;
}
else
{
// otherwise do it safely
Interlocked.Increment(ref _completionCountdown);
}
}
private void AssignCancellationToken(CancellationToken cancellationToken, Task antecedent, TaskContinuation continuation)
{
CancellationToken = cancellationToken;
try
{
cancellationToken.ThrowIfSourceDisposed();
// If an unstarted task has a valid CancellationToken that gets signalled while the task is still not queued
// we need to proactively cancel it, because it may never execute to transition itself.
// The only way to accomplish this is to register a callback on the CT.
// We exclude Promise tasks from this, because TaskCompletionSource needs to fully control the inner tasks's lifetime (i.e. not allow external cancellations)
if ((_internalOptions & (InternalTaskOptions.QueuedByRuntime | InternalTaskOptions.PromiseTask | InternalTaskOptions.LazyCancellation)) == 0)
{
if (cancellationToken.IsCancellationRequested)
{
// Fast path for an already-canceled cancellationToken
InternalCancel(false);
}
else
{
// Regular path for an uncanceled cancellationToken
CancellationTokenRegistration registration;
if (antecedent == null)
{
// if no antecedent was specified, use this task's reference as the cancellation state object
registration = cancellationToken.Register(_taskCancelCallback, this);
}
else
{
// If an antecedent was specified, pack this task, its antecedent and the TaskContinuation together as a tuple
// and use it as the cancellation state object. This will be unpacked in the cancellation callback so that
// antecedent.RemoveCancellation(continuation) can be invoked.
registration = cancellationToken.Register(_taskCancelCallback, new Tuple<Task, Task, TaskContinuation>(this, antecedent, continuation));
}
_cancellationRegistration = new StrongBox<CancellationTokenRegistration>(registration);
}
}
}
catch (Exception)
{
// If we have an exception related to our CancellationToken, then we need to subtract ourselves
// from our parent before throwing it.
if ((_parent != null)
&& ((_creationOptions & TaskCreationOptions.AttachedToParent) != 0)
&& ((_parent._creationOptions & TaskCreationOptions.DenyChildAttach) == 0)
)
{
_parent.DisregardChild();
}
throw;
}
}
/// <summary>
/// Executes the task. This method will only be called once, and handles bookeeping associated with
/// self-replicating tasks, in addition to performing necessary exception marshaling.
/// </summary>
private void Execute()
{
try
{
InnerInvoke();
}
catch (ThreadAbortException tae)
{
// Record this exception in the task's exception list
HandleException(tae);
// This is a ThreadAbortException and it will be rethrown from this catch clause, causing us to
// skip the regular Finish codepath. In order not to leave the task unfinished, we now call
// FinishThreadAbortedTask here.
FinishThreadAbortedTask(true, true);
}
catch (Exception exn)
{
// Record this exception in the task's exception list
HandleException(exn);
}
}
[SecurityCritical]
private void ExecuteWithThreadLocal()
{
// Remember the current task so we can restore it after running, and then
var previousTask = InternalCurrent;
try
{
// place the current task into TLS.
InternalCurrent = this;
var executionContext = CapturedContext;
if (executionContext == null)
{
Execute();
}
else
{
if (IsSelfReplicatingRoot || IsChildReplica)
{
CapturedContext = executionContext.CreateCopy();
}
ExecutionContext.Run(executionContext, ExecutionContextCallback, this);
}
Finish(true);
}
finally
{
InternalCurrent = previousTask;
}
}
/// <summary>
/// Returns a list of exceptions by aggregating the holder's contents. Or null if
/// no exceptions have been thrown.
/// </summary>
/// <param name="includeTaskCanceledExceptions">Whether to include a TCE if cancelled.</param>
/// <returns>An aggregate exception, or null if no exceptions have been caught.</returns>
private AggregateException GetExceptions(bool includeTaskCanceledExceptions)
{
//
// WARNING: The Task/Task<TResult>/TaskCompletionSource classes
// have all been carefully crafted to insure that GetExceptions()
// is never called while AddException() is being called. There
// are locks taken on m_contingentProperties in several places:
//
// -- Task<TResult>.TrySetException(): The lock allows the
// task to be set to Faulted state, and all exceptions to
// be recorded, in one atomic action.
//
// -- Task.Exception_get(): The lock ensures that Task<TResult>.TrySetException()
// is allowed to complete its operation before Task.Exception_get()
// can access GetExceptions().
//
// -- Task.ThrowIfExceptional(): The lock insures that Wait() will
// not attempt to call GetExceptions() while Task<TResult>.TrySetException()
// is in the process of calling AddException().
//
// For "regular" tasks, we effectively keep AddException() and GetException()
// from being called concurrently by the way that the state flows. Until
// a Task is marked Faulted, Task.Exception_get() returns null. And
// a Task is not marked Faulted until it and all of its children have
// completed, which means that all exceptions have been recorded.
//
// It might be a lot easier to follow all of this if we just required
// that all calls to GetExceptions() and AddExceptions() were made
// under a lock on m_contingentProperties. But that would also
// increase our lock occupancy time and the frequency with which we
// would need to take the lock.
//
// If you add a call to GetExceptions() anywhere in the code,
// please continue to maintain the invariant that it can't be
// called when AddException() is being called.
//
// We'll lazily create a TCE if the task has been canceled.
Exception canceledException = null;
if (includeTaskCanceledExceptions && IsCanceled)
{
// Backcompat:
// Ideally we'd just use the cached OCE from this.GetCancellationExceptionDispatchInfo()
// here. However, that would result in a potentially breaking change from .NET 4, which
// has the code here that throws a new exception instead of the original, and the EDI
// may not contain a TCE, but an OCE or any OCE-derived type, which would mean we'd be
// propagating an exception of a different type.
canceledException = new TaskCanceledException(this);
}
var exceptionsHolder = Volatile.Read(ref _exceptionsHolder);
if (exceptionsHolder != null && exceptionsHolder.ContainsFaultList)
{
// No need to lock around this, as other logic prevents the consumption of exceptions
// before they have been completely processed.
return _exceptionsHolder.CreateExceptionObject(false, canceledException);
}
if (canceledException != null)
{
// No exceptions, but there was a cancelation. Aggregate and return it.
return new AggregateException(canceledException);
}
return null;
}
/// <summary>
/// Performs whatever handling is necessary for an unhandled exception. Normally
/// this just entails adding the exception to the holder object.
/// </summary>
/// <param name="unhandledException">The exception that went unhandled.</param>
private void HandleException(Exception unhandledException)
{
Contract.Requires(unhandledException != null);
var exceptionAsOce = unhandledException as NewOperationCanceledException;
if (exceptionAsOce != null && IsCancellationRequested && CancellationToken == exceptionAsOce.CancellationToken)
{
// All conditions are satisfied for us to go into canceled state in Finish().
// Mark the acknowledgement. The exception is also stored to enable it to be
// the exception propagated from an await.
SetCancellationAcknowledged();
AddException(exceptionAsOce, /*representsCancellation:*/ true);
}
else
{
// Other exceptions, including any OCE from the task that doesn't match the tasks' own CT,
// or that gets thrown without the CT being set will be treated as an ordinary exception
// and added to the aggregate.
AddException(unhandledException);
}
}
}
public partial class Task
{
/// <summary>
/// Adds an exception to the list of exceptions this task has thrown.
/// </summary>
/// <param name="exceptionObject">An object representing either an Exception or a collection of Exceptions.</param>
/// <param name="representsCancellation">Whether the exceptionObject is an OperationCanceledException representing cancellation.</param>
internal void AddException(object exceptionObject, bool representsCancellation)
{
Contract.Requires(exceptionObject != null, "Task.AddException: Expected a non-null exception object");
//
// WARNING: A great deal of care went into ensuring that
// AddException() and GetExceptions() are never called
// simultaneously. See comment at start of GetExceptions().
//
// Lazily initialize the holder, ensuring only one thread wins.
var exceptionsHolder = Volatile.Read(ref _exceptionsHolder);
if (exceptionsHolder == null)
{
// This is the only time we write to _exceptionsHolder
var holder = new TaskExceptionHolder(this);
exceptionsHolder = Interlocked.CompareExchange(ref _exceptionsHolder, holder, null);
if (exceptionsHolder == null)
{
// The current thread did initialize _exceptionsHolder.
exceptionsHolder = holder;
}
else
{
// Another thread initialized _exceptionsHolder first.
// Suppress finalization.
holder.MarkAsHandled(false);
}
}
lock (exceptionsHolder)
{
exceptionsHolder.Add(exceptionObject, representsCancellation);
}
}
private void AddException(object exceptionObject)
{
Contract.Requires(exceptionObject != null, "Task.AddException: Expected a non-null exception object");
AddException(exceptionObject, /*representsCancellation:*/ false);
}
}
}
#endif