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.
798 lines
29 KiB
798 lines
29 KiB
5 years ago
|
#if NET20 || NET30 || NET35 || !NET_4_6
|
||
|
|
||
|
using System.Diagnostics.Contracts;
|
||
|
using LinqInternal.Core;
|
||
|
using LinqInternal.Threading;
|
||
|
using LinqInternal.Threading.Needles;
|
||
|
|
||
|
namespace System.Threading.Tasks
|
||
|
{
|
||
|
public partial class Task : IDisposable, IAsyncResult, IThreadPoolWorkItem
|
||
|
{
|
||
|
[ThreadStatic]
|
||
|
internal static Task InternalCurrent;
|
||
|
|
||
|
internal TaskScheduler ExecutingTaskScheduler;
|
||
|
protected readonly object State;
|
||
|
protected object Action;
|
||
|
private static int _lastId;
|
||
|
private readonly TaskCreationOptions _creationOptions;
|
||
|
private readonly int _id;
|
||
|
private readonly InternalTaskOptions _internalOptions;
|
||
|
private readonly Task _parent;
|
||
|
private int _isDisposed;
|
||
|
private int _status;
|
||
|
private StructNeedle<ManualResetEventSlim> _waitHandle;
|
||
|
|
||
|
public Task(Action action)
|
||
|
: this(action, null, null, default(CancellationToken), TaskCreationOptions.None, InternalTaskOptions.None, TaskScheduler.Default)
|
||
|
{
|
||
|
// Empty
|
||
|
}
|
||
|
|
||
|
public Task(Action action, CancellationToken cancellationToken)
|
||
|
: this(action, null, null, cancellationToken, TaskCreationOptions.None, InternalTaskOptions.None, TaskScheduler.Default)
|
||
|
{
|
||
|
// Empty
|
||
|
}
|
||
|
|
||
|
public Task(Action action, TaskCreationOptions creationOptions)
|
||
|
: this(action, null, InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, InternalTaskOptions.None, TaskScheduler.Default)
|
||
|
{
|
||
|
// Empty
|
||
|
}
|
||
|
|
||
|
public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
|
||
|
: this(action, null, InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, TaskScheduler.Default)
|
||
|
{
|
||
|
// Empty
|
||
|
}
|
||
|
|
||
|
internal Task(Action<object> action, object state, Task parent, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
|
||
|
: this((Delegate)action, state, parent, cancellationToken, creationOptions, internalOptions, scheduler)
|
||
|
{
|
||
|
CapturedContext = ExecutionContext.Capture();
|
||
|
}
|
||
|
|
||
|
internal Task(Action action, Task parent, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
|
||
|
: this(action, null, parent, cancellationToken, creationOptions, internalOptions, scheduler)
|
||
|
{
|
||
|
CapturedContext = ExecutionContext.Capture();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// An internal constructor used by the factory methods on task and its descendent(s).
|
||
|
/// This variant does not capture the ExecutionContext; it is up to the caller to do that.
|
||
|
/// </summary>
|
||
|
/// <param name="action">An action to execute.</param>
|
||
|
/// <param name="state">Optional state to pass to the action.</param>
|
||
|
/// <param name="parent">Parent of Task.</param>
|
||
|
/// <param name="cancellationToken">A CancellationToken for the task.</param>
|
||
|
/// <param name="scheduler">A task scheduler under which the task will run.</param>
|
||
|
/// <param name="creationOptions">Options to control its execution.</param>
|
||
|
/// <param name="internalOptions">Internal options to control its execution</param>
|
||
|
internal Task(Delegate action, object state, Task parent, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
|
||
|
{
|
||
|
if (action == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("action");
|
||
|
}
|
||
|
if (ReferenceEquals(scheduler, null))
|
||
|
{
|
||
|
throw new ArgumentNullException("scheduler");
|
||
|
}
|
||
|
Contract.EndContractBlock();
|
||
|
// This is readonly, and so must be set in the constructor
|
||
|
// Keep a link to your parent if: (A) You are attached, or (B) you are self-replicating.
|
||
|
if
|
||
|
(
|
||
|
((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
|
||
|
|| ((internalOptions & InternalTaskOptions.SelfReplicating) != 0)
|
||
|
)
|
||
|
{
|
||
|
_parent = parent;
|
||
|
}
|
||
|
_id = Interlocked.Increment(ref _lastId) - 1;
|
||
|
_status = (int)TaskStatus.Created;
|
||
|
if
|
||
|
(
|
||
|
_parent != null
|
||
|
&& ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
|
||
|
&& ((_parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0)
|
||
|
)
|
||
|
{
|
||
|
_parent.AddNewChild();
|
||
|
}
|
||
|
Action = action;
|
||
|
State = state;
|
||
|
ExecutingTaskScheduler = scheduler;
|
||
|
_waitHandle = new ManualResetEventSlim(false);
|
||
|
if ((creationOptions &
|
||
|
~(TaskCreationOptions.AttachedToParent |
|
||
|
TaskCreationOptions.LongRunning |
|
||
|
TaskCreationOptions.DenyChildAttach |
|
||
|
TaskCreationOptions.HideScheduler |
|
||
|
TaskCreationOptions.PreferFairness |
|
||
|
TaskCreationOptions.RunContinuationsAsynchronously)) != 0)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException("creationOptions");
|
||
|
}
|
||
|
// Throw exception if the user specifies both LongRunning and SelfReplicating
|
||
|
if (((creationOptions & TaskCreationOptions.LongRunning) != 0) &&
|
||
|
((internalOptions & InternalTaskOptions.SelfReplicating) != 0))
|
||
|
{
|
||
|
throw new InvalidOperationException("An attempt was made to create a LongRunning SelfReplicating task.");
|
||
|
}
|
||
|
if ((internalOptions & InternalTaskOptions.ContinuationTask) != 0)
|
||
|
{
|
||
|
// For continuation tasks or TaskCompletionSource.Tasks, begin life in the
|
||
|
// WaitingForActivation state rather than the Created state.
|
||
|
_status = (int)TaskStatus.WaitingForActivation;
|
||
|
}
|
||
|
_creationOptions = creationOptions;
|
||
|
_internalOptions = internalOptions;
|
||
|
// if we have a non-null cancellationToken, allocate the contingent properties to save it
|
||
|
// we need to do this as the very last thing in the construction path, because the CT registration could modify m_stateFlags
|
||
|
if (cancellationToken.CanBeCanceled)
|
||
|
{
|
||
|
AssignCancellationToken(cancellationToken, null, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
~Task()
|
||
|
{
|
||
|
Dispose(false);
|
||
|
}
|
||
|
|
||
|
public static int? CurrentId
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var current = InternalCurrent;
|
||
|
if (current != null)
|
||
|
{
|
||
|
return current.Id;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static TaskFactory Factory
|
||
|
{
|
||
|
get { return TaskFactory.DefaultInstance; }
|
||
|
}
|
||
|
|
||
|
public object AsyncState
|
||
|
{
|
||
|
get { return State; }
|
||
|
}
|
||
|
|
||
|
public TaskCreationOptions CreationOptions
|
||
|
{
|
||
|
get { return _creationOptions; }
|
||
|
}
|
||
|
|
||
|
public AggregateException Exception
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
AggregateException e = null;
|
||
|
|
||
|
// If you're faulted, retrieve the exception(s)
|
||
|
if (IsFaulted)
|
||
|
{
|
||
|
e = GetExceptions(false);
|
||
|
}
|
||
|
|
||
|
// Only return an exception in faulted state (skip manufactured exceptions)
|
||
|
// A "benevolent" race condition makes it possible to return null when IsFaulted is
|
||
|
// true (i.e., if IsFaulted is set just after the check to IsFaulted above).
|
||
|
Contract.Assert((e == null) || IsFaulted, "Task.Exception_get(): returning non-null value when not Faulted");
|
||
|
|
||
|
return e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WaitHandle IAsyncResult.AsyncWaitHandle
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
return _waitHandle.Value.WaitHandle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool IAsyncResult.CompletedSynchronously
|
||
|
{
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
public int Id
|
||
|
{
|
||
|
get { return _id; }
|
||
|
}
|
||
|
|
||
|
public bool IsCanceled
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var status = Thread.VolatileRead(ref _status);
|
||
|
return status == (int)TaskStatus.Canceled;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsCompleted
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var status = Status; // So PromiseCheck runs
|
||
|
return status == TaskStatus.RanToCompletion || status == TaskStatus.Faulted || status == TaskStatus.Canceled;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsFaulted
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var status = Thread.VolatileRead(ref _status);
|
||
|
return status == (int)TaskStatus.Faulted;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public TaskStatus Status
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
PromiseCheck();
|
||
|
return (TaskStatus)Thread.VolatileRead(ref _status);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal CancellationToken CancellationToken { get; set; }
|
||
|
|
||
|
internal ExecutionContext CapturedContext { get; set; }
|
||
|
|
||
|
private bool IsContinuationTask
|
||
|
{
|
||
|
get { return (_internalOptions & InternalTaskOptions.ContinuationTask) != 0; }
|
||
|
}
|
||
|
|
||
|
private bool IsPromiseTask
|
||
|
{
|
||
|
get { return (_internalOptions & InternalTaskOptions.PromiseTask) != 0; }
|
||
|
}
|
||
|
|
||
|
private bool IsScheduled
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var status = Thread.VolatileRead(ref _status);
|
||
|
return status == (int)TaskStatus.WaitingToRun || status == (int)TaskStatus.Running || status == (int)TaskStatus.WaitingForChildrenToComplete;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Dispose()
|
||
|
{
|
||
|
Dispose(true);
|
||
|
}
|
||
|
|
||
|
void IThreadPoolWorkItem.ExecuteWorkItem()
|
||
|
{
|
||
|
ExecuteEntry(false);
|
||
|
}
|
||
|
|
||
|
void IThreadPoolWorkItem.MarkAborted(ThreadAbortException exception)
|
||
|
{
|
||
|
if (!IsCompleted)
|
||
|
{
|
||
|
HandleException(exception);
|
||
|
FinishThreadAbortedTask(true, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void RunSynchronously()
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
PrivateRunSynchronously(ExecutingTaskScheduler);
|
||
|
}
|
||
|
|
||
|
public void RunSynchronously(TaskScheduler scheduler)
|
||
|
{
|
||
|
if (scheduler == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("scheduler");
|
||
|
}
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
PrivateRunSynchronously(scheduler);
|
||
|
}
|
||
|
|
||
|
public void Start()
|
||
|
{
|
||
|
if (IsCompleted)
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a task that has completed.");
|
||
|
}
|
||
|
if ((_internalOptions & InternalTaskOptions.ContinuationTask) != 0)
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a continuation task.");
|
||
|
}
|
||
|
if ((_internalOptions & InternalTaskOptions.PromiseTask) != 0)
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a promise-style task.");
|
||
|
}
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
if (!InternalStart(ExecutingTaskScheduler, false, true))
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a task that was already started.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Start(TaskScheduler scheduler)
|
||
|
{
|
||
|
if (IsCompleted)
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a task that has completed.");
|
||
|
}
|
||
|
if (scheduler == null)
|
||
|
{
|
||
|
throw new ArgumentNullException("scheduler");
|
||
|
}
|
||
|
if ((_internalOptions & InternalTaskOptions.ContinuationTask) != 0)
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a continuation task.");
|
||
|
}
|
||
|
if ((_internalOptions & InternalTaskOptions.PromiseTask) != 0)
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a promise-style task.");
|
||
|
}
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
if (!InternalStart(scheduler, false, true))
|
||
|
{
|
||
|
throw new InvalidOperationException("Start may not be called on a task that was already started.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Wait()
|
||
|
{
|
||
|
Wait(CancellationToken);
|
||
|
}
|
||
|
|
||
|
public void Wait(CancellationToken cancellationToken)
|
||
|
{
|
||
|
PrivateWait(cancellationToken, true);
|
||
|
}
|
||
|
|
||
|
public bool Wait(int milliseconds)
|
||
|
{
|
||
|
return Wait(milliseconds, CancellationToken);
|
||
|
}
|
||
|
|
||
|
public bool Wait(TimeSpan timeout)
|
||
|
{
|
||
|
var milliseconds = (int)timeout.TotalMilliseconds;
|
||
|
return Wait(milliseconds, CancellationToken);
|
||
|
}
|
||
|
|
||
|
public bool Wait(int milliseconds, CancellationToken cancellationToken)
|
||
|
{
|
||
|
if (milliseconds < -1)
|
||
|
{
|
||
|
throw new ArgumentOutOfRangeException("milliseconds");
|
||
|
}
|
||
|
if (milliseconds == -1)
|
||
|
{
|
||
|
Wait(cancellationToken);
|
||
|
return true;
|
||
|
}
|
||
|
var start = ThreadingHelper.TicksNow();
|
||
|
do
|
||
|
{
|
||
|
CancellationCheck(cancellationToken);
|
||
|
switch (Status)
|
||
|
{
|
||
|
case TaskStatus.WaitingForActivation:
|
||
|
WaitAntecedent(cancellationToken, milliseconds, start);
|
||
|
ExecutingTaskScheduler.InernalTryExecuteTaskInline(this, true);
|
||
|
break;
|
||
|
|
||
|
case TaskStatus.Created:
|
||
|
case TaskStatus.WaitingToRun:
|
||
|
case TaskStatus.Running:
|
||
|
case TaskStatus.WaitingForChildrenToComplete:
|
||
|
var waitHandle = _waitHandle.Value;
|
||
|
if (_waitHandle.IsAlive)
|
||
|
{
|
||
|
waitHandle.Wait
|
||
|
(
|
||
|
TimeSpan.FromMilliseconds
|
||
|
(
|
||
|
milliseconds - ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow() - start)
|
||
|
),
|
||
|
cancellationToken
|
||
|
);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TaskStatus.RanToCompletion:
|
||
|
return true;
|
||
|
|
||
|
case TaskStatus.Canceled:
|
||
|
ThrowIfExceptional(true);
|
||
|
return true;
|
||
|
|
||
|
case TaskStatus.Faulted:
|
||
|
ThrowIfExceptional(true);
|
||
|
return true;
|
||
|
}
|
||
|
} while (ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow() - start) < milliseconds);
|
||
|
switch (Status)
|
||
|
{
|
||
|
case TaskStatus.RanToCompletion:
|
||
|
return true;
|
||
|
|
||
|
case TaskStatus.Canceled:
|
||
|
ThrowIfExceptional(true);
|
||
|
return true;
|
||
|
|
||
|
case TaskStatus.Faulted:
|
||
|
ThrowIfExceptional(true);
|
||
|
return true;
|
||
|
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptions)
|
||
|
{
|
||
|
return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null;
|
||
|
}
|
||
|
|
||
|
internal bool ExecuteEntry(bool preventDoubleExecution)
|
||
|
{
|
||
|
if ((_internalOptions & InternalTaskOptions.PromiseTask) != 0)
|
||
|
{
|
||
|
// Promise tasks don't execute
|
||
|
return false;
|
||
|
}
|
||
|
if (!SetRunning(preventDoubleExecution))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (!IsCanceled)
|
||
|
{
|
||
|
if (CancellationToken.IsCancellationRequested)
|
||
|
{
|
||
|
Thread.VolatileWrite(ref _status, (int)TaskStatus.Canceled);
|
||
|
MarkCompleted();
|
||
|
FinishStageThree();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ExecuteWithThreadLocal();
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
internal bool InternalCancel(bool cancelNonExecutingOnly)
|
||
|
{
|
||
|
Contract.Requires((_internalOptions & InternalTaskOptions.PromiseTask) == 0, "Task.InternalCancel() did not expect promise-style task");
|
||
|
|
||
|
var cancelSucceeded = false;
|
||
|
|
||
|
RecordInternalCancellationRequest();
|
||
|
|
||
|
var status = Thread.VolatileRead(ref _status);
|
||
|
if (status <= (int)TaskStatus.WaitingToRun)
|
||
|
{
|
||
|
// Note: status may advance to TaskStatus.Running or even TaskStatus.RanToCompletion during the execution of this method
|
||
|
var scheduler = ExecutingTaskScheduler;
|
||
|
var requiresAtomicStartTransition = scheduler.RequiresAtomicStartTransition;
|
||
|
var popSucceeded = scheduler.InernalTryDequeue(this, ref requiresAtomicStartTransition);
|
||
|
if (!popSucceeded && requiresAtomicStartTransition)
|
||
|
{
|
||
|
status = Interlocked.CompareExchange(ref _status, (int)TaskStatus.Canceled, (int)TaskStatus.Created);
|
||
|
cancelSucceeded = status == (int)TaskStatus.Created;
|
||
|
status = Interlocked.CompareExchange(ref _status, (int)TaskStatus.Canceled, (int)TaskStatus.WaitingForActivation);
|
||
|
cancelSucceeded = cancelSucceeded || status == (int)TaskStatus.WaitingForActivation;
|
||
|
status = Interlocked.CompareExchange(ref _status, (int)TaskStatus.Canceled, (int)TaskStatus.WaitingToRun);
|
||
|
cancelSucceeded = cancelSucceeded || status == (int)TaskStatus.WaitingToRun;
|
||
|
}
|
||
|
}
|
||
|
if (Thread.VolatileRead(ref _status) >= (int)TaskStatus.Running && !cancelNonExecutingOnly)
|
||
|
{
|
||
|
// We are going to pretend that the cancel call came after the task finished running, but we may still set to cancel on TaskStatus.WaitingForChildrenToComplete
|
||
|
status = Interlocked.CompareExchange(ref _status, (int)TaskStatus.Canceled, (int)TaskStatus.WaitingForChildrenToComplete);
|
||
|
cancelSucceeded = cancelSucceeded || status == (int)TaskStatus.WaitingForChildrenToComplete;
|
||
|
}
|
||
|
if (cancelSucceeded)
|
||
|
{
|
||
|
MarkCompleted();
|
||
|
FinishStageThree();
|
||
|
}
|
||
|
return cancelSucceeded;
|
||
|
}
|
||
|
|
||
|
internal bool InternalStart(TaskScheduler scheduler, bool inline, bool throwSchedulerExceptions)
|
||
|
{
|
||
|
ExecutingTaskScheduler = scheduler;
|
||
|
var result = Interlocked.CompareExchange(ref _status, (int)TaskStatus.WaitingForActivation, (int)TaskStatus.Created);
|
||
|
if (result != (int)TaskStatus.Created && result != (int)TaskStatus.WaitingForActivation)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
var didInline = false;
|
||
|
try
|
||
|
{
|
||
|
if (inline)
|
||
|
{
|
||
|
// Should I worry about this task being a continuation?
|
||
|
// WaitAntecedent(CancellationToken);
|
||
|
didInline = scheduler.InernalTryExecuteTaskInline(this, IsScheduled);
|
||
|
}
|
||
|
if (!didInline)
|
||
|
{
|
||
|
scheduler.QueueTask(this);
|
||
|
Interlocked.CompareExchange(ref _status, (int)TaskStatus.WaitingToRun, (int)TaskStatus.WaitingForActivation);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PrivateWait(CancellationToken, false);
|
||
|
}
|
||
|
}
|
||
|
catch (ThreadAbortException exception)
|
||
|
{
|
||
|
if (!didInline)
|
||
|
{
|
||
|
AddException(exception);
|
||
|
FinishThreadAbortedTask(true, false);
|
||
|
}
|
||
|
}
|
||
|
catch (Exception exception)
|
||
|
{
|
||
|
var taskSchedulerException = new TaskSchedulerException(exception);
|
||
|
RecordException(taskSchedulerException);
|
||
|
if (throwSchedulerExceptions)
|
||
|
{
|
||
|
throw taskSchedulerException;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
internal void MarkCompleted()
|
||
|
{
|
||
|
var waitHandle = _waitHandle.Value;
|
||
|
if (_waitHandle.IsAlive)
|
||
|
{
|
||
|
waitHandle.Set();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void Start(TaskScheduler scheduler, bool inline)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
InternalStart(scheduler, inline, true);
|
||
|
}
|
||
|
|
||
|
internal void Start(TaskScheduler scheduler, bool inline, bool throwSchedulerExceptions)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
throw new ObjectDisposedException(GetType().FullName);
|
||
|
}
|
||
|
InternalStart(scheduler, inline, throwSchedulerExceptions);
|
||
|
}
|
||
|
|
||
|
internal bool TryStart(TaskScheduler scheduler, bool inline)
|
||
|
{
|
||
|
if (Thread.VolatileRead(ref _isDisposed) == 1)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return InternalStart(scheduler, inline, true);
|
||
|
}
|
||
|
|
||
|
protected virtual void Dispose(bool disposing)
|
||
|
{
|
||
|
if (disposing)
|
||
|
{
|
||
|
if (!IsCompleted)
|
||
|
{
|
||
|
throw new InvalidOperationException("A task may only be disposed if it is in a completion state.");
|
||
|
}
|
||
|
var waitHandle = _waitHandle.Value;
|
||
|
if (!ReferenceEquals(waitHandle, null))
|
||
|
{
|
||
|
if (!waitHandle.IsSet)
|
||
|
{
|
||
|
waitHandle.Set();
|
||
|
}
|
||
|
waitHandle.Dispose();
|
||
|
_waitHandle.Value = null;
|
||
|
}
|
||
|
}
|
||
|
Thread.VolatileWrite(ref _isDisposed, 1);
|
||
|
}
|
||
|
|
||
|
private void CancellationCheck(CancellationToken cancellationToken)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
cancellationToken.ThrowIfCancellationRequested();
|
||
|
GC.KeepAlive(cancellationToken.WaitHandle);
|
||
|
}
|
||
|
catch (NewOperationCanceledException)
|
||
|
{
|
||
|
throw new AggregateException(new TaskCanceledException(this));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void PrivateRunSynchronously(TaskScheduler scheduler)
|
||
|
{
|
||
|
// Do not Run Synchronously Continuation Tasks
|
||
|
if (IsContinuationTask)
|
||
|
{
|
||
|
throw new InvalidOperationException("RunSynchronously may not be called on a continuation task.");
|
||
|
}
|
||
|
// Do not Run Synchronously Promise Tasks
|
||
|
if (IsPromiseTask)
|
||
|
{
|
||
|
throw new InvalidOperationException("RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.");
|
||
|
}
|
||
|
// Can't call this method on a task that has already completed
|
||
|
if (IsCompleted)
|
||
|
{
|
||
|
throw new InvalidOperationException("RunSynchronously may not be called on a task that has already completed.");
|
||
|
}
|
||
|
// Make sure that Task only gets started once. Or else throw an exception.
|
||
|
if (!InternalStart(scheduler, true, true))
|
||
|
{
|
||
|
throw new InvalidOperationException("RunSynchronously may not be called on a task that was already started.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void PrivateWait(CancellationToken cancellationToken, bool throwIfExceptional)
|
||
|
{
|
||
|
var done = false;
|
||
|
while (!done)
|
||
|
{
|
||
|
CancellationCheck(cancellationToken);
|
||
|
switch (Status)
|
||
|
{
|
||
|
case TaskStatus.WaitingToRun:
|
||
|
WaitAntecedent(CancellationToken);
|
||
|
ExecutingTaskScheduler.InernalTryExecuteTaskInline(this, true);
|
||
|
break;
|
||
|
|
||
|
case TaskStatus.Created:
|
||
|
case TaskStatus.WaitingForActivation:
|
||
|
case TaskStatus.Running:
|
||
|
case TaskStatus.WaitingForChildrenToComplete:
|
||
|
var waitHandle = _waitHandle.Value;
|
||
|
if (_waitHandle.IsAlive)
|
||
|
{
|
||
|
waitHandle.Wait(cancellationToken);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TaskStatus.RanToCompletion:
|
||
|
case TaskStatus.Canceled:
|
||
|
done = true;
|
||
|
break;
|
||
|
|
||
|
case TaskStatus.Faulted:
|
||
|
if (throwIfExceptional)
|
||
|
{
|
||
|
ThrowIfExceptional(true);
|
||
|
}
|
||
|
done = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
#if DEBUG
|
||
|
if (!IsCompleted)
|
||
|
{
|
||
|
Diagnostics.Debugger.Break();
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
private void RecordException(TaskSchedulerException taskSchedulerException)
|
||
|
{
|
||
|
AddException(taskSchedulerException);
|
||
|
Finish(false);
|
||
|
if ((_internalOptions & InternalTaskOptions.ContinuationTask) != 0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (_exceptionsHolder != null)
|
||
|
{
|
||
|
Contract.Assert(_exceptionsHolder.ContainsFaultList, "Expected _exceptionsHolder to have faults recorded.");
|
||
|
_exceptionsHolder.MarkAsHandled(false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Contract.Assert(false, "Expected _exceptionsHolder to exist.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool SetRunning(bool preventDoubleExecution)
|
||
|
{
|
||
|
// For this method to be called the Task must have been scheduled,
|
||
|
// this means that _status must be at least TaskStatus.WaitingForActivation (1),
|
||
|
// if status is:
|
||
|
// TaskStatus.WaitingForActivation (1) -> ok
|
||
|
// WaitingToRun (2) -> ok
|
||
|
// TaskStatus.Running (3) -> ok if preventDoubleExecution = false
|
||
|
// TaskStatus.WaitingForChildrenToComplete (4) -> ok if preventDoubleExecution = false
|
||
|
// TaskStatus.RanToCompletion (5) -> ok if preventDoubleExecution = false
|
||
|
// TaskStatus.Canceled (6) -> not ok
|
||
|
// TaskStatus.Faulted (7) -> -> ok if preventDoubleExecution = false
|
||
|
var spinWait = new SpinWait();
|
||
|
while (true)
|
||
|
{
|
||
|
var lastValue = Thread.VolatileRead(ref _status);
|
||
|
if ((preventDoubleExecution && lastValue >= 3) || lastValue == 6)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
var tmp = Interlocked.CompareExchange(ref _status, 3, lastValue);
|
||
|
if (tmp == lastValue)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
spinWait.SpinOnce();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void WaitAntecedent(CancellationToken cancellationToken)
|
||
|
{
|
||
|
if (IsContinuationTask)
|
||
|
{
|
||
|
var antecedent = ((IContinuationTask)this).Antecedent;
|
||
|
if (antecedent != null)
|
||
|
{
|
||
|
antecedent.Wait(cancellationToken);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void WaitAntecedent(CancellationToken cancellationToken, int milliseconds, long start)
|
||
|
{
|
||
|
if (IsContinuationTask)
|
||
|
{
|
||
|
var antecedent = ((IContinuationTask)this).Antecedent;
|
||
|
if (antecedent != null)
|
||
|
{
|
||
|
antecedent.Wait
|
||
|
(
|
||
|
milliseconds - (int)ThreadingHelper.Milliseconds(ThreadingHelper.TicksNow() - start),
|
||
|
cancellationToken
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|