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.
457 lines
18 KiB
457 lines
18 KiB
#if NET20 || NET30 || NET35 || !NET_4_6 |
|
|
|
using System.Collections.Generic; |
|
using System.Diagnostics.Contracts; |
|
using System.Runtime.ExceptionServices; |
|
using LinqInternal; |
|
|
|
namespace System.Threading.Tasks |
|
{ |
|
internal interface ITaskCompletionAction |
|
{ |
|
void Invoke(Task completingTask); |
|
} |
|
|
|
public partial class Task |
|
{ |
|
internal sealed class CompleteOnInvokePromise : Task<Task>, ITaskCompletionAction |
|
{ |
|
private int _firstTaskAlreadyCompleted; |
|
private ICollection<Task> _tasks; // must track this for cleanup |
|
|
|
public CompleteOnInvokePromise(ICollection<Task> tasks) |
|
{ |
|
Contract.Requires(tasks != null, "Expected non-null collection of tasks"); |
|
Contract.Requires(tasks.Count > 0, "Expected a non-zero length task array"); |
|
_tasks = tasks; |
|
} |
|
|
|
public void Invoke(Task completingTask) |
|
{ |
|
if (Interlocked.CompareExchange(ref _firstTaskAlreadyCompleted, 1, 0) == 0) |
|
{ |
|
var success = TrySetResult(completingTask); |
|
Contract.Assert(success, "Only one task should have gotten to this point, and thus this must be successful."); |
|
|
|
// We need to remove continuations that may be left straggling on other tasks. |
|
// Otherwise, repeated calls to WhenAny using the same task could leak actions. |
|
// This may also help to avoided unnecessary invocations of this whenComplete delegate. |
|
// Note that we may be attempting to remove a continuation from a task that hasn't had it |
|
// added yet; while there's overhead there, the operation won't hurt anything. |
|
foreach (var task in _tasks) |
|
{ |
|
// if an element was erroneously nulled out concurrently, just skip it; worst case is we don't remove a continuation |
|
if (task != null && !task.IsCompleted) |
|
{ |
|
task.RemoveContinuation(this); |
|
} |
|
} |
|
_tasks = null; |
|
} |
|
} |
|
} |
|
|
|
private sealed class WhenAllCore : ITaskCompletionAction, IDisposable |
|
{ |
|
private int _count; |
|
private Action _done; |
|
private int _ready; |
|
private Task[] _tasks; |
|
|
|
internal WhenAllCore(ICollection<Task> tasks, Action done) |
|
{ |
|
Contract.Requires(tasks != null, "Expected non-null collection of tasks"); |
|
Contract.Requires(tasks.Count > 0, "Expected a non-zero length task array"); |
|
_done = done; |
|
_tasks = new Task[tasks.Count]; |
|
foreach (var task in tasks) |
|
{ |
|
AddTask(task); |
|
} |
|
Ready(); |
|
} |
|
|
|
public bool IsDone |
|
{ |
|
get { return Interlocked.CompareExchange(ref _done, null, null) == null; } |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
var tasks = Interlocked.Exchange(ref _tasks, null); |
|
if (Interlocked.CompareExchange(ref _done, null, null) == null) |
|
{ |
|
return; |
|
} |
|
var incomplete = false; |
|
foreach (var task in tasks) |
|
{ |
|
if (task == null) |
|
{ |
|
continue; |
|
} |
|
if (task.IsCompleted) |
|
{ |
|
continue; |
|
} |
|
task.RemoveContinuation(this); |
|
incomplete = true; |
|
} |
|
if (!incomplete) |
|
{ |
|
Done(); |
|
} |
|
} |
|
|
|
public void Invoke(Task completingTask) |
|
{ |
|
var tasks = Interlocked.CompareExchange(ref _tasks, null, null); |
|
if (tasks == null) |
|
{ |
|
return; |
|
} |
|
// Do not use IndexOf |
|
for (var index = 0; index < tasks.Length; index++) |
|
{ |
|
if (tasks[index] == completingTask) |
|
{ |
|
tasks[index] = null; |
|
break; |
|
} |
|
} |
|
var count = Interlocked.Decrement(ref _count); |
|
if (count == 0 && Thread.VolatileRead(ref _ready) == 1) |
|
{ |
|
Done(); |
|
} |
|
} |
|
|
|
private void AddTask(Task awaitedTask) |
|
{ |
|
Contract.Requires(Thread.VolatileRead(ref _ready) == 0); |
|
if (awaitedTask.Status != TaskStatus.RanToCompletion) |
|
{ |
|
Interlocked.Increment(ref _count); |
|
if (awaitedTask.AddTaskContinuation(this, /*addBeforeOthers:*/ true)) |
|
{ |
|
var tasks = Interlocked.CompareExchange(ref _tasks, null, null); |
|
if (tasks == null) |
|
{ |
|
return; |
|
} |
|
var index = Array.IndexOf(tasks, null); |
|
while (Interlocked.CompareExchange(ref tasks[index], awaitedTask, null) != null) |
|
{ |
|
index = (index + 1) % tasks.Length; |
|
} |
|
} |
|
else |
|
{ |
|
Interlocked.Decrement(ref _count); |
|
} |
|
} |
|
} |
|
|
|
private void Done() |
|
{ |
|
var done = Interlocked.Exchange(ref _done, null); |
|
if (done != null) |
|
{ |
|
done(); |
|
} |
|
} |
|
|
|
private void Ready() |
|
{ |
|
Thread.VolatileWrite(ref _ready, 1); |
|
if (Thread.VolatileRead(ref _count) == 0) |
|
{ |
|
Done(); |
|
} |
|
} |
|
} |
|
|
|
// A Task<VoidTaskResult> that gets completed when all of its constituent tasks complete. |
|
// Completion logic will analyze the antecedents in order to choose completion status. |
|
// This type allows us to replace this logic: |
|
// Task<VoidTaskResult> promise = new Task<VoidTaskResult>(...); |
|
// Action<Task> completionAction = delegate { <completion logic>}; |
|
// TaskFactory.CommonCWAllLogic(tasksCopy).AddCompletionAction(completionAction); |
|
// return promise; |
|
// which involves several allocations, with this logic: |
|
// return new WhenAllPromise(tasksCopy); |
|
// which saves a couple of allocations and enables debugger notification specialization. |
|
// |
|
// Used in InternalWhenAll(Task[]) |
|
private sealed class WhenAllPromise : Task<VoidStruct>, ITaskCompletionAction |
|
{ |
|
private readonly Task[] _tasks; |
|
private int _count; |
|
private int _done; |
|
private int _ready; |
|
|
|
internal WhenAllPromise(Task[] tasks) |
|
{ |
|
Contract.Requires(tasks != null, "Expected a non-null task array"); |
|
Contract.Requires(tasks.Length > 0, "Expected a non-zero length task array"); |
|
_tasks = tasks; |
|
foreach (var task in _tasks) |
|
{ |
|
AddTask(task); |
|
} |
|
Ready(); |
|
} |
|
|
|
/// <summary> |
|
/// Returns whether we should notify the debugger of a wait completion. This returns |
|
/// true iff at least one constituent task has its bit set. |
|
/// </summary> |
|
internal override bool ShouldNotifyDebuggerOfWaitCompletion |
|
{ |
|
get { return base.ShouldNotifyDebuggerOfWaitCompletion && AnyTaskRequiresNotifyDebuggerOfWaitCompletion(_tasks); } |
|
} |
|
|
|
public void Invoke(Task completedTask) |
|
{ |
|
var count = Interlocked.Decrement(ref _count); |
|
if (count == 0) |
|
{ |
|
if (Thread.VolatileRead(ref _ready) == 1) |
|
{ |
|
Done(); |
|
} |
|
} |
|
} |
|
|
|
private void AddTask(Task awaitedTask) |
|
{ |
|
Contract.Requires(Thread.VolatileRead(ref _ready) == 0); |
|
Interlocked.Increment(ref _count); |
|
if (!awaitedTask.AddTaskContinuation(this, /*addBeforeOthers:*/ true)) |
|
{ |
|
Interlocked.Decrement(ref _count); |
|
} |
|
} |
|
|
|
private void Done() |
|
{ |
|
var done = Interlocked.Exchange(ref _done, 1); |
|
if (done == 0) |
|
{ |
|
PrivateDone(); |
|
} |
|
} |
|
|
|
private void PrivateDone() |
|
{ |
|
// Set up some accounting variables |
|
List<ExceptionDispatchInfo> observedExceptions = null; |
|
Task canceledTask = null; |
|
// Loop through antecedents: |
|
// If any one of them faults, the result will be faulted |
|
// If none fault, but at least one is canceled, the result will be canceled |
|
// If none fault or are canceled, then result will be RanToCompletion |
|
for (var index = 0; index < _tasks.Length; index++) |
|
{ |
|
var task = _tasks[index]; |
|
if (task == null) |
|
{ |
|
Contract.Assert(false, "Constituent task in WhenAll should never be null"); |
|
throw new InvalidOperationException("Constituent task in WhenAll should never be null"); |
|
} |
|
if (task.IsFaulted) |
|
{ |
|
if (observedExceptions == null) |
|
{ |
|
observedExceptions = new List<ExceptionDispatchInfo>(); |
|
} |
|
observedExceptions.AddRange(task._exceptionsHolder.GetExceptionDispatchInfos()); |
|
} |
|
else if (task.IsCanceled) |
|
{ |
|
if (canceledTask == null) |
|
{ |
|
canceledTask = task; // use the first task that's canceled |
|
} |
|
} |
|
// Regardless of completion state, if the task has its debug bit set, transfer it to the |
|
// WhenAll task. We must do this before we complete the task. |
|
if (task.IsWaitNotificationEnabled) |
|
{ |
|
SetNotificationForWaitCompletion(/*enabled:*/ true); |
|
} |
|
else |
|
{ |
|
_tasks[index] = null; // avoid holding onto tasks unnecessarily |
|
} |
|
} |
|
if (observedExceptions != null) |
|
{ |
|
Contract.Assert(observedExceptions.Count > 0, "Expected at least one exception"); |
|
//We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there |
|
TrySetException(observedExceptions); |
|
} |
|
else if (canceledTask != null) |
|
{ |
|
TrySetCanceledPromise(canceledTask.CancellationToken); |
|
} |
|
else |
|
{ |
|
TrySetResult(default(VoidStruct)); |
|
} |
|
} |
|
|
|
private void Ready() |
|
{ |
|
Thread.VolatileWrite(ref _ready, 1); |
|
if (Thread.VolatileRead(ref _count) == 0) |
|
{ |
|
Done(); |
|
} |
|
} |
|
} |
|
|
|
// A Task<T> that gets completed when all of its constituent tasks complete. |
|
// Completion logic will analyze the antecedents in order to choose completion status. |
|
// See comments for non-generic version of WhenAllPromise class. |
|
// |
|
// Used in InternalWhenAll<TResult>(Task<TResult>[]) |
|
private sealed class WhenAllPromise<T> : Task<T[]>, ITaskCompletionAction |
|
{ |
|
private readonly Task<T>[] _tasks; |
|
private int _count; |
|
private int _done; |
|
private int _ready; |
|
|
|
internal WhenAllPromise(Task<T>[] tasks) |
|
{ |
|
Contract.Requires(tasks != null, "Expected a non-null task array"); |
|
Contract.Requires(tasks.Length > 0, "Expected a non-zero length task array"); |
|
_tasks = tasks; |
|
foreach (var task in _tasks) |
|
{ |
|
AddTask(task); |
|
} |
|
Ready(); |
|
} |
|
|
|
/// <summary> |
|
/// Returns whether we should notify the debugger of a wait completion. This returns |
|
/// true iff at least one constituent task has its bit set. |
|
/// </summary> |
|
internal override bool ShouldNotifyDebuggerOfWaitCompletion |
|
{ |
|
get { return base.ShouldNotifyDebuggerOfWaitCompletion && AnyTaskRequiresNotifyDebuggerOfWaitCompletion(_tasks); } |
|
} |
|
|
|
public void Invoke(Task completedTask) |
|
{ |
|
var count = Interlocked.Decrement(ref _count); |
|
if (count == 0) |
|
{ |
|
if (Thread.VolatileRead(ref _ready) == 1) |
|
{ |
|
Done(); |
|
} |
|
} |
|
} |
|
|
|
private void AddTask(Task awaitedTask) |
|
{ |
|
Contract.Requires(Thread.VolatileRead(ref _ready) == 0); |
|
Interlocked.Increment(ref _count); |
|
if (!awaitedTask.AddTaskContinuation(this, /*addBeforeOthers:*/ true)) |
|
{ |
|
Interlocked.Decrement(ref _count); |
|
} |
|
} |
|
|
|
private void Done() |
|
{ |
|
var done = Interlocked.Exchange(ref _done, 1); |
|
if (done == 0) |
|
{ |
|
PrivateDone(); |
|
} |
|
} |
|
|
|
private void PrivateDone() |
|
{ |
|
// Set up some accounting variables |
|
var results = new T[_tasks.Length]; |
|
List<ExceptionDispatchInfo> observedExceptions = null; |
|
Task canceledTask = null; |
|
// Loop through antecedents: |
|
// If any one of them faults, the result will be faulted |
|
// If none fault, but at least one is canceled, the result will be canceled |
|
// If none fault or are canceled, then result will be RanToCompletion |
|
for (var index = 0; index < _tasks.Length; index++) |
|
{ |
|
var task = _tasks[index]; |
|
if (task == null) |
|
{ |
|
Contract.Assert(false, "Constituent task in WhenAll should never be null"); |
|
throw new InvalidOperationException("Constituent task in WhenAll should never be null"); |
|
} |
|
if (task.IsFaulted) |
|
{ |
|
if (observedExceptions == null) |
|
{ |
|
observedExceptions = new List<ExceptionDispatchInfo>(); |
|
} |
|
observedExceptions.AddRange(task._exceptionsHolder.GetExceptionDispatchInfos()); |
|
} |
|
else if (task.IsCanceled) |
|
{ |
|
if (canceledTask == null) |
|
{ |
|
canceledTask = task; // use the first task that's canceled |
|
} |
|
} |
|
else |
|
{ |
|
Contract.Assert(task.Status == TaskStatus.RanToCompletion); |
|
results[index] = task.Result; |
|
} |
|
// Regardless of completion state, if the task has its debug bit set, transfer it to the |
|
// WhenAll task. We must do this before we complete the task. |
|
if (task.IsWaitNotificationEnabled) |
|
{ |
|
SetNotificationForWaitCompletion(/*enabled:*/ true); |
|
} |
|
else |
|
{ |
|
_tasks[index] = null; // avoid holding onto tasks unnecessarily |
|
} |
|
} |
|
if (observedExceptions != null) |
|
{ |
|
Contract.Assert(observedExceptions.Count > 0, "Expected at least one exception"); |
|
|
|
//We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there |
|
|
|
TrySetException(observedExceptions); |
|
} |
|
else if (canceledTask != null) |
|
{ |
|
TrySetCanceledPromise(canceledTask.CancellationToken); |
|
} |
|
else |
|
{ |
|
TrySetResult(results); |
|
} |
|
} |
|
|
|
private void Ready() |
|
{ |
|
Thread.VolatileWrite(ref _ready, 1); |
|
if (Thread.VolatileRead(ref _count) == 0) |
|
{ |
|
Done(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endif |