+//
// Task.cs
//
+// Authors:
+// Marek Safar <marek.safar@gmail.com>
+// Jérémie Laval <jeremie dot laval at xamarin dot com>
+//
// Copyright (c) 2008 Jérémie "Garuma" Laval
+// Copyright 2011-2013 Xamarin Inc (http://www.xamarin.com).
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
//
//
-#if NET_4_0 || MOBILE
+#if NET_4_0
using System;
using System.Threading;
using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
namespace System.Threading.Tasks
{
- [System.Diagnostics.DebuggerDisplay ("Id = {Id}, Status = {Status}, Method = {DisplayActionMethod}")]
-// [System.Diagnostics.DebuggerTypeProxy ("System.Threading.Tasks.SystemThreadingTasks_TaskDebugView")]
+ [System.Diagnostics.DebuggerDisplay ("Id = {Id}, Status = {Status}")]
+ [System.Diagnostics.DebuggerTypeProxy (typeof (TaskDebuggerView))]
public class Task : IDisposable, IAsyncResult
{
// With this attribute each thread has its own value so that it's correct for our Schedule code
// and for Parent property.
[System.ThreadStatic]
- static Task current;
- [System.ThreadStatic]
- static Action<Task> childWorkAdder;
+ static Task current;
- Task parent;
+ // parent is the outer task in which this task is created
+ readonly Task parent;
+ // A reference to a Task on which this continuation is attached to
+ Task contAncestor;
static int id = -1;
- static TaskFactory defaultFactory = new TaskFactory ();
-
- CountdownEvent childTasks = new CountdownEvent (1);
+ static readonly TaskFactory defaultFactory = new TaskFactory ();
+
+ CountdownEvent childTasks;
int taskId;
- TaskCreationOptions taskCreationOptions;
+ TaskCreationOptions creationOptions;
- TaskScheduler scheduler;
+ internal TaskScheduler scheduler;
- ManualResetEventSlim schedWait = new ManualResetEventSlim (false);
-
- volatile AggregateException exception;
- volatile bool exceptionObserved;
- ConcurrentQueue<AggregateException> childExceptions;
+ TaskExceptionSlot exSlot;
+ ManualResetEvent wait_handle;
TaskStatus status;
-
- Action<object> action;
- Action simpleAction;
+
+ TaskActionInvoker invoker;
object state;
- AtomicBooleanValue executing;
+ internal AtomicBooleanValue executing;
- ConcurrentQueue<EventHandler> completed;
+ TaskCompletionQueue<IContinuation> continuations;
CancellationToken token;
+ CancellationTokenRegistration? cancellationRegistration;
+
+ internal const TaskCreationOptions WorkerTaskNotSupportedOptions = TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness;
const TaskCreationOptions MaxTaskCreationOptions =
+#if NET_4_5
+ TaskCreationOptions.DenyChildAttach | TaskCreationOptions.HideScheduler |
+#endif
TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent;
- public Task (Action action) : this (action, TaskCreationOptions.None)
+ public Task (Action action)
+ : this (action, TaskCreationOptions.None)
{
}
- public Task (Action action, TaskCreationOptions creationOptions) : this (action, CancellationToken.None, creationOptions)
+ public Task (Action action, TaskCreationOptions creationOptions)
+ : this (action, CancellationToken.None, creationOptions)
{
}
- public Task (Action action, CancellationToken cancellationToken) : this (action, cancellationToken, TaskCreationOptions.None)
+ public Task (Action action, CancellationToken cancellationToken)
+ : this (action, cancellationToken, TaskCreationOptions.None)
{
}
public Task (Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
- : this (null, null, cancellationToken, creationOptions, current)
+ : this (TaskActionInvoker.Create (action), null, cancellationToken, creationOptions, current)
{
if (action == null)
throw new ArgumentNullException ("action");
if (creationOptions > MaxTaskCreationOptions || creationOptions < TaskCreationOptions.None)
throw new ArgumentOutOfRangeException ("creationOptions");
- this.simpleAction = action;
}
- public Task (Action<object> action, object state) : this (action, state, TaskCreationOptions.None)
+ public Task (Action<object> action, object state)
+ : this (action, state, TaskCreationOptions.None)
{
}
}
public Task (Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
- : this (action, state, cancellationToken, creationOptions, current)
+ : this (TaskActionInvoker.Create (action), state, cancellationToken, creationOptions, current)
{
if (action == null)
throw new ArgumentNullException ("action");
throw new ArgumentOutOfRangeException ("creationOptions");
}
- internal Task (Action<object> action,
- object state,
- CancellationToken cancellationToken,
- TaskCreationOptions creationOptions,
- Task parent)
+ internal Task (TaskActionInvoker invoker, object state, CancellationToken cancellationToken,
+ TaskCreationOptions creationOptions, Task parent = null, Task contAncestor = null, bool ignoreCancellation = false)
{
- this.taskCreationOptions = creationOptions;
- this.action = action;
- this.state = state;
- this.taskId = Interlocked.Increment (ref id);
- this.status = cancellationToken.IsCancellationRequested ? TaskStatus.Canceled : TaskStatus.Created;
- this.token = cancellationToken;
- this.parent = parent;
-
- // Process taskCreationOptions
- if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null)
+ this.invoker = invoker;
+ this.creationOptions = creationOptions;
+ this.state = state;
+ this.taskId = Interlocked.Increment (ref id);
+ this.token = cancellationToken;
+ this.parent = parent = parent == null ? current : parent;
+ this.contAncestor = contAncestor;
+ this.status = cancellationToken.IsCancellationRequested && !ignoreCancellation ? TaskStatus.Canceled : TaskStatus.Created;
+
+ // Process creationOptions
+#if NET_4_5
+ if (parent != null && HasFlag (creationOptions, TaskCreationOptions.AttachedToParent)
+ && !HasFlag (parent.CreationOptions, TaskCreationOptions.DenyChildAttach))
+#else
+ if (parent != null && HasFlag (creationOptions, TaskCreationOptions.AttachedToParent))
+#endif
parent.AddChild ();
- }
- ~Task ()
- {
- if (exception != null && !exceptionObserved)
- throw exception;
+ if (token.CanBeCanceled && !ignoreCancellation)
+ cancellationRegistration = token.Register (l => ((Task) l).CancelReal (), this);
}
- bool CheckTaskOptions (TaskCreationOptions opt, TaskCreationOptions member)
+ static bool HasFlag (TaskCreationOptions opt, TaskCreationOptions member)
{
return (opt & member) == member;
}
public void Start (TaskScheduler scheduler)
{
+ if (scheduler == null)
+ throw new ArgumentNullException ("scheduler");
+
if (status >= TaskStatus.WaitingToRun)
throw new InvalidOperationException ("The Task is not in a valid state to be started.");
+
+ if (IsContinuation)
+ throw new InvalidOperationException ("Start may not be called on a continuation task");
+
SetupScheduler (scheduler);
Schedule ();
}
internal void SetupScheduler (TaskScheduler scheduler)
{
this.scheduler = scheduler;
- status = TaskStatus.WaitingForActivation;
- schedWait.Set ();
+ Status = TaskStatus.WaitingForActivation;
}
public void RunSynchronously ()
public void RunSynchronously (TaskScheduler scheduler)
{
+ if (scheduler == null)
+ throw new ArgumentNullException ("scheduler");
+
if (Status > TaskStatus.WaitingForActivation)
throw new InvalidOperationException ("The task is not in a valid state to be started");
+ if (IsContinuation)
+ throw new InvalidOperationException ("RunSynchronously may not be called on a continuation task");
+
+ RunSynchronouslyCore (scheduler);
+ }
+
+ internal void RunSynchronouslyCore (TaskScheduler scheduler)
+ {
SetupScheduler (scheduler);
+ Status = TaskStatus.WaitingToRun;
try {
- if (scheduler.RunInline (this))
+ if (scheduler.RunInline (this, false))
return;
} catch (Exception inner) {
throw new TaskSchedulerException (inner);
}
- Start (scheduler);
+ Schedule ();
Wait ();
}
#endregion
public Task ContinueWith (Action<Task> continuationAction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
{
- Task continuation = new Task ((o) => continuationAction ((Task)o),
- this,
- cancellationToken,
- GetCreationOptions (continuationOptions),
- this);
+ if (continuationAction == null)
+ throw new ArgumentNullException ("continuationAction");
+ if (scheduler == null)
+ throw new ArgumentNullException ("scheduler");
+
+ return ContinueWith (TaskActionInvoker.Create (continuationAction), cancellationToken, continuationOptions, scheduler);
+ }
+
+ internal Task ContinueWith (TaskActionInvoker invoker, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+ {
+ var lazyCancellation = false;
+#if NET_4_5
+ lazyCancellation = (continuationOptions & TaskContinuationOptions.LazyCancellation) > 0;
+#endif
+ var continuation = new Task (invoker, null, cancellationToken, GetCreationOptions (continuationOptions), null, this, lazyCancellation);
ContinueWithCore (continuation, continuationOptions, scheduler);
return continuation;
if (scheduler == null)
throw new ArgumentNullException ("scheduler");
- Task<TResult> t = new Task<TResult> ((o) => continuationFunction ((Task)o),
- this,
- cancellationToken,
- GetCreationOptions (continuationOptions),
- this);
-
- ContinueWithCore (t, continuationOptions, scheduler);
-
- return t;
+ return ContinueWith<TResult> (TaskActionInvoker.Create (continuationFunction), cancellationToken, continuationOptions, scheduler);
}
-
- internal void ContinueWithCore (Task continuation, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+
+ internal Task<TResult> ContinueWith<TResult> (TaskActionInvoker invoker, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
{
- ContinueWithCore (continuation, continuationOptions, scheduler, null);
+ var lazyCancellation = false;
+#if NET_4_5
+ lazyCancellation = (continuationOptions & TaskContinuationOptions.LazyCancellation) > 0;
+#endif
+ var continuation = new Task<TResult> (invoker, null, cancellationToken, GetCreationOptions (continuationOptions), parent, this, lazyCancellation);
+ ContinueWithCore (continuation, continuationOptions, scheduler);
+
+ return continuation;
}
-
- internal void ContinueWithCore (Task continuation, TaskContinuationOptions kind,
- TaskScheduler scheduler, Func<bool> predicate)
+
+ internal void ContinueWithCore (Task continuation, TaskContinuationOptions options, TaskScheduler scheduler)
{
+ const TaskContinuationOptions wrongRan = TaskContinuationOptions.NotOnRanToCompletion | TaskContinuationOptions.OnlyOnRanToCompletion;
+ const TaskContinuationOptions wrongCanceled = TaskContinuationOptions.NotOnCanceled | TaskContinuationOptions.OnlyOnCanceled;
+ const TaskContinuationOptions wrongFaulted = TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.OnlyOnFaulted;
+
+ if (((options & wrongRan) == wrongRan) || ((options & wrongCanceled) == wrongCanceled) || ((options & wrongFaulted) == wrongFaulted))
+ throw new ArgumentException ("continuationOptions", "Some options are mutually exclusive");
+
// Already set the scheduler so that user can call Wait and that sort of stuff
continuation.scheduler = scheduler;
- continuation.schedWait.Set ();
- continuation.status = TaskStatus.WaitingForActivation;
-
- AtomicBoolean launched = new AtomicBoolean ();
- EventHandler action = delegate (object sender, EventArgs e) {
- if (launched.TryRelaxedSet ()) {
- if (predicate != null && !predicate ())
- return;
-
- if (!ContinuationStatusCheck (kind)) {
- continuation.CancelReal ();
- continuation.Dispose ();
-
- return;
- }
-
- CheckAndSchedule (continuation, kind, scheduler, sender == null);
- }
- };
-
+ continuation.Status = TaskStatus.WaitingForActivation;
+
+ ContinueWith (new TaskContinuation (continuation, options));
+ }
+
+ internal void ContinueWith (IContinuation continuation)
+ {
if (IsCompleted) {
- action (null, EventArgs.Empty);
+ continuation.Execute ();
return;
}
- if (completed == null)
- Interlocked.CompareExchange (ref completed, new ConcurrentQueue<EventHandler> (), null);
- completed.Enqueue (action);
+ continuations.Add (continuation);
// Retry in case completion was achieved but event adding was too late
- if (IsCompleted)
- action (null, EventArgs.Empty);
+ if (IsCompleted && continuations.Remove (continuation))
+ continuation.Execute ();
}
-
- bool ContinuationStatusCheck (TaskContinuationOptions kind)
- {
- if (kind == TaskContinuationOptions.None)
- return true;
-
- int kindCode = (int)kind;
-
- if (kindCode >= ((int)TaskContinuationOptions.NotOnRanToCompletion)) {
- // Remove other options
- kind &= ~(TaskContinuationOptions.PreferFairness
- | TaskContinuationOptions.LongRunning
- | TaskContinuationOptions.AttachedToParent
- | TaskContinuationOptions.ExecuteSynchronously);
-
- if (status == TaskStatus.Canceled) {
- if (kind == TaskContinuationOptions.NotOnCanceled)
- return false;
- if (kind == TaskContinuationOptions.OnlyOnFaulted)
- return false;
- if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
- return false;
- } else if (status == TaskStatus.Faulted) {
- if (kind == TaskContinuationOptions.NotOnFaulted)
- return false;
- if (kind == TaskContinuationOptions.OnlyOnCanceled)
- return false;
- if (kind == TaskContinuationOptions.OnlyOnRanToCompletion)
- return false;
- } else if (status == TaskStatus.RanToCompletion) {
- if (kind == TaskContinuationOptions.NotOnRanToCompletion)
- return false;
- if (kind == TaskContinuationOptions.OnlyOnFaulted)
- return false;
- if (kind == TaskContinuationOptions.OnlyOnCanceled)
- return false;
- }
- }
-
- return true;
- }
-
- void CheckAndSchedule (Task continuation, TaskContinuationOptions options, TaskScheduler scheduler, bool fromCaller)
+ internal void RemoveContinuation (IContinuation continuation)
{
- if ((options & TaskContinuationOptions.ExecuteSynchronously) > 0)
- continuation.RunSynchronously (scheduler);
- else
- continuation.Start (scheduler);
+ continuations.Remove (continuation);
}
-
- internal TaskCreationOptions GetCreationOptions (TaskContinuationOptions kind)
+
+ static internal TaskCreationOptions GetCreationOptions (TaskContinuationOptions kind)
{
TaskCreationOptions options = TaskCreationOptions.None;
if ((kind & TaskContinuationOptions.AttachedToParent) > 0)
#region Internal and protected thingies
internal void Schedule ()
{
- status = TaskStatus.WaitingToRun;
-
- // If worker is null it means it is a local one, revert to the old behavior
- // If TaskScheduler.Current is not being used, the scheduler was explicitly provided, so we must use that
- if (scheduler != TaskScheduler.Current || childWorkAdder == null || CheckTaskOptions (taskCreationOptions, TaskCreationOptions.PreferFairness)) {
- scheduler.QueueTask (this);
- } else {
- /* Like the semantic of the ABP paper describe it, we add ourselves to the bottom
- * of our Parent Task's ThreadWorker deque. It's ok to do that since we are in
- * the correct Thread during the creation
- */
- childWorkAdder (this);
- }
+ Status = TaskStatus.WaitingToRun;
+ scheduler.QueueTask (this);
}
void ThreadStart ()
if (!executing.TryRelaxedSet ())
return;
+ // Disable CancellationToken direct cancellation
+ if (cancellationRegistration != null) {
+ cancellationRegistration.Value.Dispose ();
+ cancellationRegistration = null;
+ }
+
+ // If Task are ran inline on the same thread we might trash these values
+ var saveCurrent = current;
+ var saveScheduler = TaskScheduler.Current;
+
current = this;
+#if NET_4_5
+ TaskScheduler.Current = HasFlag (creationOptions, TaskCreationOptions.HideScheduler) ? TaskScheduler.Default : scheduler;
+#else
TaskScheduler.Current = scheduler;
+#endif
if (!token.IsCancellationRequested) {
} else {
CancelReal ();
}
-
+
+ if (saveCurrent != null)
+ current = saveCurrent;
+ if (saveScheduler != null)
+ TaskScheduler.Current = saveScheduler;
Finish ();
}
-
- internal void Execute (Action<Task> childAdder)
+
+ internal bool TrySetCanceled ()
+ {
+ if (IsCompleted)
+ return false;
+
+ if (!executing.TryRelaxedSet ()) {
+ var sw = new SpinWait ();
+ while (!IsCompleted)
+ sw.SpinOnce ();
+
+ return false;
+ }
+
+ CancelReal ();
+ return true;
+ }
+
+ internal bool TrySetException (AggregateException aggregate)
+ {
+ if (IsCompleted)
+ return false;
+
+ if (!executing.TryRelaxedSet ()) {
+ var sw = new SpinWait ();
+ while (!IsCompleted)
+ sw.SpinOnce ();
+
+ return false;
+ }
+
+ HandleGenericException (aggregate);
+ return true;
+ }
+
+ internal bool TrySetExceptionObserved ()
+ {
+ if (exSlot != null) {
+ exSlot.Observed = true;
+ return true;
+ }
+ return false;
+ }
+
+ internal void Execute ()
{
- childWorkAdder = childAdder;
ThreadStart ();
}
internal void AddChild ()
{
+ if (childTasks == null)
+ Interlocked.CompareExchange (ref childTasks, new CountdownEvent (1), null);
childTasks.AddCount ();
}
internal void ChildCompleted (AggregateException childEx)
{
if (childEx != null) {
- if (childExceptions == null)
- Interlocked.CompareExchange (ref childExceptions, new ConcurrentQueue<AggregateException> (), null);
- childExceptions.Enqueue (childEx);
+ if (ExceptionSlot.ChildExceptions == null)
+ Interlocked.CompareExchange (ref ExceptionSlot.ChildExceptions, new ConcurrentQueue<AggregateException> (), null);
+ ExceptionSlot.ChildExceptions.Enqueue (childEx);
}
if (childTasks.Signal () && status == TaskStatus.WaitingForChildrenToComplete) {
- status = TaskStatus.RanToCompletion;
ProcessChildExceptions ();
+ Status = exSlot == null ? TaskStatus.RanToCompletion : TaskStatus.Faulted;
ProcessCompleteDelegates ();
+ if (parent != null &&
+#if NET_4_5
+ !HasFlag (parent.CreationOptions, TaskCreationOptions.DenyChildAttach) &&
+#endif
+ HasFlag (creationOptions, TaskCreationOptions.AttachedToParent))
+ parent.ChildCompleted (this.Exception);
}
}
- internal virtual void InnerInvoke ()
+ void InnerInvoke ()
{
- if (action == null && simpleAction != null)
- simpleAction ();
- else if (action != null)
- action (state);
- // Set action to null so that the GC can collect the delegate and thus
- // any big object references that the user might have captured in an anonymous method
- action = null;
- simpleAction = null;
- state = null;
+ if (IsContinuation) {
+ var ancestor = contAncestor;
+ contAncestor = null;
+ invoker.Invoke (ancestor, state, this);
+ } else {
+ invoker.Invoke (this, state, this);
+ }
}
internal void Finish ()
{
- // If there wasn't any child created in the task we set the CountdownEvent
- childTasks.Signal ();
+ // If there was children created and they all finished, we set the countdown
+ if (childTasks != null) {
+ if (childTasks.Signal ())
+ ProcessChildExceptions (true);
+ }
// Don't override Canceled or Faulted
if (status == TaskStatus.Running) {
- if (childTasks.IsSet)
- status = TaskStatus.RanToCompletion;
+ if (childTasks == null || childTasks.IsSet)
+ Status = TaskStatus.RanToCompletion;
else
- status = TaskStatus.WaitingForChildrenToComplete;
+ Status = TaskStatus.WaitingForChildrenToComplete;
}
-
- if (status != TaskStatus.WaitingForChildrenToComplete)
- ProcessCompleteDelegates ();
-
- // Reset the current thingies
- current = null;
- TaskScheduler.Current = null;
-
+
+ if (wait_handle != null)
+ wait_handle.Set ();
+
// Tell parent that we are finished
- if (CheckTaskOptions (taskCreationOptions, TaskCreationOptions.AttachedToParent) && parent != null) {
+ if (parent != null && HasFlag (creationOptions, TaskCreationOptions.AttachedToParent) &&
+#if NET_4_5
+ !HasFlag (parent.CreationOptions, TaskCreationOptions.DenyChildAttach) &&
+#endif
+ status != TaskStatus.WaitingForChildrenToComplete) {
parent.ChildCompleted (this.Exception);
}
+
+ // Completions are already processed when task is canceled or faulted
+ if (status == TaskStatus.RanToCompletion)
+ ProcessCompleteDelegates ();
+
+ // Reset the current thingies
+ if (current == this)
+ current = null;
+ if (TaskScheduler.Current == scheduler)
+ TaskScheduler.Current = null;
+
+ if (cancellationRegistration.HasValue)
+ cancellationRegistration.Value.Dispose ();
}
void ProcessCompleteDelegates ()
{
- if (completed == null)
- return;
-
- EventHandler handler;
- while (completed.TryDequeue (out handler))
- handler (this, EventArgs.Empty);
+ if (continuations.HasElements) {
+ IContinuation continuation;
+ while (continuations.TryGetNextCompletion (out continuation))
+ continuation.Execute ();
+ }
}
- void ProcessChildExceptions ()
+ void ProcessChildExceptions (bool isParent = false)
{
- if (childExceptions == null)
+ if (exSlot == null || exSlot.ChildExceptions == null)
return;
- if (exception == null)
- exception = new AggregateException ();
+ if (ExceptionSlot.Exception == null)
+ exSlot.Exception = new AggregateException ();
AggregateException childEx;
- while (childExceptions.TryDequeue (out childEx))
- exception.AddChildException (childEx);
+ while (exSlot.ChildExceptions.TryDequeue (out childEx))
+ exSlot.Exception.AddChildException (childEx);
+
+ if (isParent) {
+ Status = TaskStatus.Faulted;
+ ProcessCompleteDelegates ();
+ }
}
#endregion
internal void CancelReal ()
{
- status = TaskStatus.Canceled;
+ Status = TaskStatus.Canceled;
+
+ if (wait_handle != null)
+ wait_handle.Set ();
+
+ ProcessCompleteDelegates ();
}
- internal void HandleGenericException (Exception e)
+ void HandleGenericException (Exception e)
{
HandleGenericException (new AggregateException (e));
}
- internal void HandleGenericException (AggregateException e)
+ void HandleGenericException (AggregateException e)
{
- exception = e;
+ ExceptionSlot.Exception = e;
Thread.MemoryBarrier ();
- status = TaskStatus.Faulted;
- if (scheduler != null && scheduler.FireUnobservedEvent (exception).Observed)
- exceptionObserved = true;
+ Status = TaskStatus.Faulted;
+
+ if (wait_handle != null)
+ wait_handle.Set ();
+
+ ProcessCompleteDelegates ();
+ }
+
+ internal bool WaitOnChildren ()
+ {
+ if (Status == TaskStatus.WaitingForChildrenToComplete && childTasks != null) {
+ childTasks.Wait ();
+ return true;
+ }
+ return false;
}
public void Wait ()
{
- if (scheduler == null)
- schedWait.Wait ();
-
- if (!IsCompleted)
- scheduler.ParticipateUntil (this);
- if (exception != null)
- throw exception;
- if (IsCanceled)
- throw new AggregateException (new TaskCanceledException (this));
+ Wait (Timeout.Infinite, CancellationToken.None);
}
public void Wait (CancellationToken cancellationToken)
{
- Wait (-1, cancellationToken);
+ Wait (Timeout.Infinite, cancellationToken);
}
public bool Wait (TimeSpan timeout)
if (millisecondsTimeout < -1)
throw new ArgumentOutOfRangeException ("millisecondsTimeout");
- if (millisecondsTimeout == -1 && token == CancellationToken.None) {
- Wait ();
+ bool result = WaitCore (millisecondsTimeout, cancellationToken);
+
+ if (IsCanceled)
+ throw new AggregateException (new TaskCanceledException (this));
+
+ var exception = Exception;
+ if (exception != null)
+ throw exception;
+
+ return result;
+ }
+
+ internal bool WaitCore (int millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ if (IsCompleted)
return true;
- }
- Watch watch = Watch.StartNew ();
+ // If the task is ready to be run and we were supposed to wait on it indefinitely without cancellation, just run it
+ if (Status == TaskStatus.WaitingToRun && millisecondsTimeout == Timeout.Infinite && scheduler != null && !cancellationToken.CanBeCanceled)
+ scheduler.RunInline (this, true);
- if (scheduler == null) {
- schedWait.Wait (millisecondsTimeout, cancellationToken);
- millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
- }
+ bool result = true;
- ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
- if (cancellationToken != CancellationToken.None) {
- cancellationToken.Register (predicateEvt.Set);
- cancellationToken.ThrowIfCancellationRequested ();
+ if (!IsCompleted) {
+ var continuation = new ManualResetContinuation ();
+ try {
+ ContinueWith (continuation);
+ result = continuation.Event.Wait (millisecondsTimeout, cancellationToken);
+ } finally {
+ if (!result)
+ RemoveContinuation (continuation);
+ continuation.Dispose ();
+ }
}
- bool result = scheduler.ParticipateUntil (this, predicateEvt, millisecondsTimeout);
-
- if (exception != null)
- throw exception;
- if (IsCanceled)
- throw new AggregateException (new TaskCanceledException (this));
-
- return !result;
+ return result;
}
public static void WaitAll (params Task[] tasks)
{
- if (tasks == null)
- throw new ArgumentNullException ("tasks");
-
- foreach (var t in tasks) {
- if (t == null)
- throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
- t.Wait ();
- }
+ WaitAll (tasks, Timeout.Infinite, CancellationToken.None);
}
public static void WaitAll (Task[] tasks, CancellationToken cancellationToken)
{
- if (tasks == null)
- throw new ArgumentNullException ("tasks");
-
- foreach (var t in tasks) {
- if (t == null)
- throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
-
- t.Wait (cancellationToken);
- }
+ WaitAll (tasks, Timeout.Infinite, cancellationToken);
}
public static bool WaitAll (Task[] tasks, TimeSpan timeout)
{
- if (tasks == null)
- throw new ArgumentNullException ("tasks");
-
- bool result = true;
- foreach (var t in tasks) {
- if (t == null)
- throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
-
- result &= t.Wait (timeout);
- }
- return result;
+ return WaitAll (tasks, CheckTimeout (timeout), CancellationToken.None);
}
public static bool WaitAll (Task[] tasks, int millisecondsTimeout)
{
- if (tasks == null)
- throw new ArgumentNullException ("tasks");
-
- bool result = true;
- foreach (var t in tasks) {
- if (t == null)
- throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
-
- result &= t.Wait (millisecondsTimeout);
- }
- return result;
+ return WaitAll (tasks, millisecondsTimeout, CancellationToken.None);
}
public static bool WaitAll (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
if (tasks == null)
throw new ArgumentNullException ("tasks");
-
+
bool result = true;
foreach (var t in tasks) {
if (t == null)
- throw new ArgumentNullException ("tasks", "the tasks argument contains a null element");
+ throw new ArgumentException ("tasks", "the tasks argument contains a null element");
+
+ result &= t.Status == TaskStatus.RanToCompletion;
+ }
- result &= t.Wait (millisecondsTimeout, cancellationToken);
+ if (!result) {
+ var continuation = new CountdownContinuation (tasks.Length);
+ try {
+ foreach (var t in tasks)
+ t.ContinueWith (continuation);
+
+ result = continuation.Event.Wait (millisecondsTimeout, cancellationToken);
+ } finally {
+ List<Exception> exceptions = null;
+
+ foreach (var t in tasks) {
+ if (result) {
+ if (t.Status == TaskStatus.RanToCompletion)
+ continue;
+ if (exceptions == null)
+ exceptions = new List<Exception> ();
+ if (t.Exception != null)
+ exceptions.AddRange (t.Exception.InnerExceptions);
+ else
+ exceptions.Add (new TaskCanceledException (t));
+ } else {
+ t.RemoveContinuation (continuation);
+ }
+ }
+
+ continuation.Dispose ();
+
+ if (exceptions != null)
+ throw new AggregateException (exceptions);
+ }
}
+
return result;
}
public static int WaitAny (params Task[] tasks)
{
- return WaitAny (tasks, -1, CancellationToken.None);
+ return WaitAny (tasks, Timeout.Infinite, CancellationToken.None);
}
public static int WaitAny (Task[] tasks, TimeSpan timeout)
public static int WaitAny (Task[] tasks, int millisecondsTimeout)
{
- if (millisecondsTimeout < -1)
- throw new ArgumentOutOfRangeException ("millisecondsTimeout");
-
- if (millisecondsTimeout == -1)
- return WaitAny (tasks);
-
return WaitAny (tasks, millisecondsTimeout, CancellationToken.None);
}
public static int WaitAny (Task[] tasks, CancellationToken cancellationToken)
{
- return WaitAny (tasks, -1, cancellationToken);
+ return WaitAny (tasks, Timeout.Infinite, cancellationToken);
}
public static int WaitAny (Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
if (tasks == null)
throw new ArgumentNullException ("tasks");
- if (tasks.Length == 0)
- throw new ArgumentException ("tasks is empty", "tasks");
- if (tasks.Length == 1) {
- tasks[0].Wait (millisecondsTimeout, cancellationToken);
- return 0;
- }
-
- int numFinished = 0;
- int indexFirstFinished = -1;
- int index = 0;
- TaskScheduler sched = null;
- Task task = null;
- Watch watch = Watch.StartNew ();
- ManualResetEventSlim predicateEvt = new ManualResetEventSlim (false);
-
- foreach (Task t in tasks) {
- int indexResult = index++;
- t.ContinueWith (delegate {
- if (numFinished >= 1)
- return;
- int result = Interlocked.Increment (ref numFinished);
-
- // Check if we are the first to have finished
- if (result == 1)
- indexFirstFinished = indexResult;
-
- // Stop waiting
- predicateEvt.Set ();
- }, TaskContinuationOptions.ExecuteSynchronously);
-
- if (sched == null && t.scheduler != null) {
- task = t;
- sched = t.scheduler;
- }
- }
-
- // If none of task have a scheduler we are forced to wait for at least one to start
- if (sched == null) {
- var handles = Array.ConvertAll (tasks, t => t.schedWait.WaitHandle);
- int shandle = -1;
- if ((shandle = WaitHandle.WaitAny (handles, millisecondsTimeout)) == WaitHandle.WaitTimeout)
- return -1;
- sched = tasks[shandle].scheduler;
- task = tasks[shandle];
- millisecondsTimeout = ComputeTimeout (millisecondsTimeout, watch);
- }
+ if (millisecondsTimeout < -1)
+ throw new ArgumentOutOfRangeException ("millisecondsTimeout");
+ CheckForNullTasks (tasks);
- // One task already finished
- if (indexFirstFinished != -1)
- return indexFirstFinished;
+ if (tasks.Length > 0) {
+ var continuation = new ManualResetContinuation ();
+ bool result = false;
+ try {
+ for (int i = 0; i < tasks.Length; i++) {
+ var t = tasks[i];
+ if (t.IsCompleted)
+ return i;
+ t.ContinueWith (continuation);
+ }
- if (cancellationToken != CancellationToken.None) {
- cancellationToken.Register (predicateEvt.Set);
- cancellationToken.ThrowIfCancellationRequested ();
+ if (!(result = continuation.Event.Wait (millisecondsTimeout, cancellationToken)))
+ return -1;
+ } finally {
+ if (!result)
+ foreach (var t in tasks)
+ t.RemoveContinuation (continuation);
+ continuation.Dispose ();
+ }
}
- sched.ParticipateUntil (task, predicateEvt, millisecondsTimeout);
-
- // Index update is still not done
- if (indexFirstFinished == -1) {
- SpinWait wait = new SpinWait ();
- while (indexFirstFinished == -1)
- wait.SpinOnce ();
+ int firstFinished = -1;
+ for (int i = 0; i < tasks.Length; i++) {
+ var t = tasks[i];
+ if (t.IsCompleted) {
+ firstFinished = i;
+ break;
+ }
}
- return indexFirstFinished;
+ return firstFinished;
}
static int CheckTimeout (TimeSpan timeout)
}
}
- static int ComputeTimeout (int millisecondsTimeout, Watch watch)
+ static void CheckForNullTasks (Task[] tasks)
{
- return millisecondsTimeout == -1 ? -1 : (int)Math.Max (watch.ElapsedMilliseconds - millisecondsTimeout, 1);
+ foreach (var t in tasks)
+ if (t == null)
+ throw new ArgumentException ("tasks", "the tasks argument contains a null element");
}
-
#endregion
#region Dispose
public void Dispose ()
{
- Dispose (true);
+ Dispose (true);
}
protected virtual void Dispose (bool disposing)
// Set action to null so that the GC can collect the delegate and thus
// any big object references that the user might have captured in a anonymous method
if (disposing) {
- action = null;
+ invoker = null;
state = null;
+ if (cancellationRegistration != null)
+ cancellationRegistration.Value.Dispose ();
+ if (wait_handle != null)
+ wait_handle.Dispose ();
}
}
#endregion
+
+#if NET_4_5
+ public
+#else
+ internal
+#endif
+ Task ContinueWith (Action<Task, object> continuationAction, object state, CancellationToken cancellationToken,
+ TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+ {
+ if (continuationAction == null)
+ throw new ArgumentNullException ("continuationAction");
+ if (scheduler == null)
+ throw new ArgumentNullException ("scheduler");
+
+ Task continuation = new Task (TaskActionInvoker.Create (continuationAction),
+ state, cancellationToken,
+ GetCreationOptions (continuationOptions),
+ parent,
+ this);
+ ContinueWithCore (continuation, continuationOptions, scheduler);
+
+ return continuation;
+ }
+#if NET_4_5
+
+ public ConfiguredTaskAwaitable ConfigureAwait (bool continueOnCapturedContext)
+ {
+ return new ConfiguredTaskAwaitable (this, continueOnCapturedContext);
+ }
+
+ public Task ContinueWith (Action<Task, object> continuationAction, object state)
+ {
+ return ContinueWith (continuationAction, state, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Current);
+ }
+
+ public Task ContinueWith (Action<Task, object> continuationAction, object state, CancellationToken cancellationToken)
+ {
+ return ContinueWith (continuationAction, state, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
+ }
+
+ public Task ContinueWith (Action<Task, object> continuationAction, object state, TaskContinuationOptions continuationOptions)
+ {
+ return ContinueWith (continuationAction, state, CancellationToken.None, continuationOptions, TaskScheduler.Current);
+ }
+
+ public Task ContinueWith (Action<Task, object> continuationAction, object state, TaskScheduler scheduler)
+ {
+ return ContinueWith (continuationAction, state, CancellationToken.None, TaskContinuationOptions.None, scheduler);
+ }
+
+ public Task<TResult> ContinueWith<TResult> (Func<Task, object, TResult> continuationFunction, object state)
+ {
+ return ContinueWith<TResult> (continuationFunction, state, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Current);
+ }
+
+ public Task<TResult> ContinueWith<TResult> (Func<Task, object, TResult> continuationFunction, object state, TaskContinuationOptions continuationOptions)
+ {
+ return ContinueWith<TResult> (continuationFunction, state, CancellationToken.None, continuationOptions, TaskScheduler.Current);
+ }
+
+ public Task<TResult> ContinueWith<TResult> (Func<Task, object, TResult> continuationFunction, object state, CancellationToken cancellationToken)
+ {
+ return ContinueWith<TResult> (continuationFunction, state, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current);
+ }
+
+ public Task<TResult> ContinueWith<TResult> (Func<Task, object, TResult> continuationFunction, object state, TaskScheduler scheduler)
+ {
+ return ContinueWith<TResult> (continuationFunction, state, CancellationToken.None, TaskContinuationOptions.None, scheduler);
+ }
+
+ public Task<TResult> ContinueWith<TResult> (Func<Task, object, TResult> continuationFunction, object state, CancellationToken cancellationToken,
+ TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
+ {
+ if (continuationFunction == null)
+ throw new ArgumentNullException ("continuationFunction");
+ if (scheduler == null)
+ throw new ArgumentNullException ("scheduler");
+
+ var t = new Task<TResult> (TaskActionInvoker.Create (continuationFunction),
+ state,
+ cancellationToken,
+ GetCreationOptions (continuationOptions),
+ parent,
+ this);
+
+ ContinueWithCore (t, continuationOptions, scheduler);
+
+ return t;
+ }
+
+ public static Task Delay (int millisecondsDelay)
+ {
+ return Delay (millisecondsDelay, CancellationToken.None);
+ }
+
+ public static Task Delay (TimeSpan delay)
+ {
+ return Delay (CheckTimeout (delay), CancellationToken.None);
+ }
+
+ public static Task Delay (TimeSpan delay, CancellationToken cancellationToken)
+ {
+ return Delay (CheckTimeout (delay), cancellationToken);
+ }
+
+ public static Task Delay (int millisecondsDelay, CancellationToken cancellationToken)
+ {
+ if (millisecondsDelay < -1)
+ throw new ArgumentOutOfRangeException ("millisecondsDelay");
+
+ if (cancellationToken.IsCancellationRequested)
+ return TaskConstants.Canceled;
+
+ var task = new Task (TaskActionInvoker.Empty, null, cancellationToken, TaskCreationOptions.None, null, null);
+ task.SetupScheduler (TaskScheduler.Default);
+
+ if (millisecondsDelay != Timeout.Infinite) {
+ var timer = new Timer (delegate (object state) {
+ var t = (Task) state;
+ if (t.Status == TaskStatus.WaitingForActivation) {
+ t.Status = TaskStatus.Running;
+ t.Finish ();
+ }
+ }, task, millisecondsDelay, -1);
+
+ task.ContinueWith (new DisposeContinuation (timer));
+ }
+
+ return task;
+ }
+
+ public static Task<TResult> FromResult<TResult> (TResult result)
+ {
+ var tcs = new TaskCompletionSource<TResult> ();
+ tcs.SetResult (result);
+ return tcs.Task;
+ }
+
+ public TaskAwaiter GetAwaiter ()
+ {
+ return new TaskAwaiter (this);
+ }
+
+ public static Task Run (Action action)
+ {
+ return Run (action, CancellationToken.None);
+ }
+
+ public static Task Run (Action action, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return TaskConstants.Canceled;
+
+ return Task.Factory.StartNew (action, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public static Task Run (Func<Task> function)
+ {
+ return Run (function, CancellationToken.None);
+ }
+
+ public static Task Run (Func<Task> function, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return TaskConstants.Canceled;
+
+ return TaskExtensionsImpl.Unwrap (Task.Factory.StartNew (function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
+ }
+
+ public static Task<TResult> Run<TResult> (Func<TResult> function)
+ {
+ return Run (function, CancellationToken.None);
+ }
+
+ public static Task<TResult> Run<TResult> (Func<TResult> function, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return TaskConstants<TResult>.Canceled;
+
+ return Task.Factory.StartNew (function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+ }
+
+ public static Task<TResult> Run<TResult> (Func<Task<TResult>> function)
+ {
+ return Run (function, CancellationToken.None);
+ }
+
+ public static Task<TResult> Run<TResult> (Func<Task<TResult>> function, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return TaskConstants<TResult>.Canceled;
+
+ return TaskExtensionsImpl.Unwrap (Task.Factory.StartNew (function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default));
+ }
+
+ public static Task WhenAll (params Task[] tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ return WhenAllCore (tasks);
+ }
+
+ public static Task WhenAll (IEnumerable<Task> tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ // Call ToList on input enumeration or we end up
+ // enumerating it more than once
+ return WhenAllCore (new List<Task> (tasks));
+ }
+
+ public static Task<TResult[]> WhenAll<TResult> (params Task<TResult>[] tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ return WhenAllCore<TResult> (tasks);
+ }
+
+ public static Task<TResult[]> WhenAll<TResult> (IEnumerable<Task<TResult>> tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ // Call ToList on input enumeration or we end up
+ // enumerating it more than once
+ return WhenAllCore<TResult> (new List<Task<TResult>> (tasks));
+ }
+
+ internal static Task<TResult[]> WhenAllCore<TResult> (IList<Task<TResult>> tasks)
+ {
+ if (tasks.Count == 0)
+ return FromResult(new TResult[0]);
+
+ foreach (var t in tasks) {
+ if (t == null)
+ throw new ArgumentException ("tasks", "the tasks argument contains a null element");
+ }
+
+ var task = new Task<TResult[]> (TaskActionInvoker.Empty, null, CancellationToken.None, TaskCreationOptions.None, null, TaskConstants.Finished);
+ task.SetupScheduler (TaskScheduler.Current);
+
+ var continuation = new WhenAllContinuation<TResult> (task, tasks);
+ foreach (var t in tasks)
+ t.ContinueWith (continuation);
+
+ return task;
+ }
+
+ public static Task<Task> WhenAny (params Task[] tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ return WhenAnyCore (tasks);
+ }
+
+ public static Task<Task> WhenAny (IEnumerable<Task> tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ return WhenAnyCore (new List<Task> (tasks));
+ }
+
+ public static Task<Task<TResult>> WhenAny<TResult> (params Task<TResult>[] tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ return WhenAnyCore<TResult> (tasks);
+ }
+
+ public static Task<Task<TResult>> WhenAny<TResult> (IEnumerable<Task<TResult>> tasks)
+ {
+ if (tasks == null)
+ throw new ArgumentNullException ("tasks");
+
+ return WhenAnyCore<TResult> (new List<Task<TResult>> (tasks));
+ }
+
+ static Task<Task<TResult>> WhenAnyCore<TResult> (IList<Task<TResult>> tasks)
+ {
+ if (tasks.Count == 0)
+ throw new ArgumentException ("The tasks argument contains no tasks", "tasks");
+
+ int completed_index = -1;
+ for (int i = 0; i < tasks.Count; ++i) {
+ var t = tasks[i];
+ if (t == null)
+ throw new ArgumentException ("tasks", "the tasks argument contains a null element");
+
+ if (t.IsCompleted && completed_index < 0)
+ completed_index = i;
+ }
+
+ var task = new Task<Task<TResult>> (TaskActionInvoker.Empty, null, CancellationToken.None, TaskCreationOptions.None, null, TaskConstants.Finished);
+
+ if (completed_index > 0) {
+ task.TrySetResult (tasks[completed_index]);
+ return task;
+ }
+
+ task.SetupScheduler (TaskScheduler.Current);
+
+ var continuation = new WhenAnyContinuation<Task<TResult>> (task, tasks);
+ foreach (var t in tasks)
+ t.ContinueWith (continuation);
+
+ return task;
+ }
+
+ public static YieldAwaitable Yield ()
+ {
+ return new YieldAwaitable ();
+ }
+#endif
+
+ internal static Task WhenAllCore (IList<Task> tasks)
+ {
+ bool all_completed = true;
+ foreach (var t in tasks) {
+ if (t == null)
+ throw new ArgumentException ("tasks", "the tasks argument contains a null element");
+
+ all_completed &= t.Status == TaskStatus.RanToCompletion;
+ }
+
+ if (all_completed)
+ return TaskConstants.Finished;
+
+ var task = new Task (TaskActionInvoker.Empty, null, CancellationToken.None, TaskCreationOptions.None, null, TaskConstants.Finished);
+ task.SetupScheduler (TaskScheduler.Current);
+
+ var continuation = new WhenAllContinuation (task, tasks);
+ foreach (var t in tasks)
+ t.ContinueWith (continuation);
+
+ return task;
+ }
+
+ internal static Task<Task> WhenAnyCore (IList<Task> tasks)
+ {
+ if (tasks.Count == 0)
+ throw new ArgumentException ("The tasks argument contains no tasks", "tasks");
+
+ int completed_index = -1;
+ for (int i = 0; i < tasks.Count; ++i) {
+ var t = tasks [i];
+ if (t == null)
+ throw new ArgumentException ("tasks", "the tasks argument contains a null element");
+
+ if (t.IsCompleted && completed_index < 0)
+ completed_index = i;
+ }
+
+ var task = new Task<Task> (TaskActionInvoker.Empty, null, CancellationToken.None, TaskCreationOptions.None, null, TaskConstants.Finished);
+
+ if (completed_index > 0) {
+ task.TrySetResult (tasks[completed_index]);
+ return task;
+ }
+
+ task.SetupScheduler (TaskScheduler.Current);
+
+ var continuation = new WhenAnyContinuation<Task> (task, tasks);
+ foreach (var t in tasks)
+ t.ContinueWith (continuation);
+
+ return task;
+ }
#region Properties
+
+ internal CancellationToken CancellationToken {
+ get {
+ return token;
+ }
+ }
+
public static TaskFactory Factory {
get {
return defaultFactory;
public AggregateException Exception {
get {
- exceptionObserved = true;
-
- return exception;
- }
- internal set {
- exception = value;
+ if (exSlot == null)
+ return null;
+ exSlot.Observed = true;
+ return exSlot.Exception;
}
}
public bool IsCompleted {
get {
- return status == TaskStatus.RanToCompletion ||
- status == TaskStatus.Canceled || status == TaskStatus.Faulted;
+ return status >= TaskStatus.RanToCompletion;
}
}
public TaskCreationOptions CreationOptions {
get {
- return taskCreationOptions;
+ return creationOptions & MaxTaskCreationOptions;
}
}
}
internal set {
status = value;
+ Thread.MemoryBarrier ();
+ }
+ }
+
+ TaskExceptionSlot ExceptionSlot {
+ get {
+ if (exSlot != null)
+ return exSlot;
+ Interlocked.CompareExchange (ref exSlot, new TaskExceptionSlot (this), null);
+ return exSlot;
}
}
WaitHandle IAsyncResult.AsyncWaitHandle {
get {
- return null;
+ if (invoker == null)
+ throw new ObjectDisposedException (GetType ().ToString ());
+
+ if (wait_handle == null)
+ Interlocked.CompareExchange (ref wait_handle, new ManualResetEvent (IsCompleted), null);
+
+ return wait_handle;
}
}
}
}
- internal Task Parent {
+ bool IsContinuation {
+ get {
+ return contAncestor != null;
+ }
+ }
+
+ internal Task ContinuationAncestor {
get {
- return parent;
+ return contAncestor;
}
}
- string DisplayActionMethod {
+ internal string DisplayActionMethod {
get {
- Delegate d = simpleAction ?? (Delegate) action;
+ Delegate d = invoker.Action;
return d == null ? "<none>" : d.Method.ToString ();
}
}