#if NET20 || NET30 || NET35 || !NET_4_6
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
namespace System.Threading.Tasks
{
public partial class Task
{
private int _waitNotificationEnabled;
///
/// Determines whether we should inform the debugger that we're ending a join with a task.
/// This should only be called if the debugger notification bit is set, as it is has some cost,
/// namely it is a virtual call (however calling it if the bit is not set is not functionally
/// harmful). Derived implementations may choose to only conditionally call down to this base
/// implementation.
///
internal virtual bool ShouldNotifyDebuggerOfWaitCompletion // ideally would be familyAndAssembly, but that can't be done in C#
{
get
{
// It's theoretically possible but extremely rare that this assert could fire because the
// bit was unset between the time that it was checked and this method was called.
// It's so remote a chance that it's worth having the assert to protect against misuse.
var isWaitNotificationEnabled = IsWaitNotificationEnabled;
Contract.Assert(isWaitNotificationEnabled, "Should only be called if the wait completion bit is set.");
return isWaitNotificationEnabled;
}
}
///
/// Calls the debugger notification method if the right bit is set and if
/// the task itself allows for the notification to proceed.
///
/// true if the debugger was notified; otherwise, false.
internal bool NotifyDebuggerOfWaitCompletionIfNecessary()
{
// Notify the debugger if of any of the tasks we've waited on requires notification
if (IsWaitNotificationEnabled && ShouldNotifyDebuggerOfWaitCompletion)
{
NotifyDebuggerOfWaitCompletion();
return true;
}
return false;
}
/// Placeholder method used as a breakpoint target by the debugger. Must not be inlined or optimized.
/// All joins with a task should end up calling this if their debugger notification bit is set.
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private void NotifyDebuggerOfWaitCompletion()
{
// It's theoretically possible but extremely rare that this assert could fire because the
// bit was unset between the time that it was checked and this method was called.
// It's so remote a chance that it's worth having the assert to protect against misuse.
Contract.Assert(IsWaitNotificationEnabled, "Should only be called if the wait completion bit is set.");
// Now that we're notifying the debugger, clear the bit. The debugger should do this anyway,
// but this adds a bit of protection in case it fails to, and given that the debugger is involved,
// the overhead here for the interlocked is negligable. We do still rely on the debugger
// to clear bits, as this doesn't recursively clear bits in the case of, for example, WhenAny.
SetNotificationForWaitCompletion(/*enabled:*/ false);
}
///
/// Sets or clears the TASK_STATE_WAIT_COMPLETION_NOTIFICATION state bit.
/// The debugger sets this bit to aid it in "stepping out" of an async method body.
/// If enabled is true, this must only be called on a task that has not yet been completed.
/// If enabled is false, this may be called on completed tasks.
/// Either way, it should only be used for promise-style tasks.
///
/// true to set the bit; false to unset the bit.
internal void SetNotificationForWaitCompletion(bool enabled)
{
Contract.Assert(IsPromiseTask, "Should only be used for promise-style tasks"); // hasn't been vetted on other kinds as there hasn't been a need
Thread.VolatileWrite(ref _waitNotificationEnabled, enabled ? 1 : 0);
}
/// Gets whether the task's debugger notification for wait completion bit is set.
/// true if the bit is set; false if it's not set.
internal bool IsWaitNotificationEnabled // internal only to enable unit tests; would otherwise be private
{
get { return Thread.VolatileRead(ref _waitNotificationEnabled) == 1; }
}
///
/// Waits for all of the provided objects to complete execution.
///
///
/// An array of instances on which to wait.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// At least one of the instances was canceled -or- an exception was thrown during
/// the execution of at least one of the instances.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static void WaitAll(params Task[] tasks)
{
#if DEBUG
var waitResult =
#endif
WaitAll(tasks, Timeout.Infinite);
#if DEBUG
Contract.Assert(waitResult, "expected wait to succeed");
#endif
}
///
/// Waits for all of the provided objects to complete execution.
///
///
/// true if all of the instances completed execution within the allotted time;
/// otherwise, false.
///
///
/// An array of instances on which to wait.
///
///
/// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// At least one of the instances was canceled -or- an exception was thrown during
/// the execution of at least one of the instances.
///
///
/// is a negative number other than -1 milliseconds, which represents an
/// infinite time-out -or- timeout is greater than
/// .
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static bool WaitAll(Task[] tasks, TimeSpan timeout)
{
var totalMilliseconds = (long)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
{
throw new ArgumentOutOfRangeException("timeout");
}
return WaitAll(tasks, (int)totalMilliseconds);
}
///
/// Waits for all of the provided objects to complete execution.
///
///
/// true if all of the instances completed execution within the allotted time;
/// otherwise, false.
///
///
/// The number of milliseconds to wait, or (-1) to
/// wait indefinitely.
/// An array of instances on which to wait.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// At least one of the instances was canceled -or- an exception was thrown during
/// the execution of at least one of the instances.
///
///
/// is a negative number other than -1, which represents an
/// infinite time-out.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static bool WaitAll(Task[] tasks, int millisecondsTimeout)
{
return WaitAll(tasks, millisecondsTimeout, default(CancellationToken));
}
///
/// Waits for all of the provided objects to complete execution.
///
///
/// true if all of the instances completed execution within the allotted time;
/// otherwise, false.
///
///
/// An array of instances on which to wait.
///
///
/// A to observe while waiting for the tasks to complete.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// At least one of the instances was canceled -or- an exception was thrown during
/// the execution of at least one of the instances.
///
///
/// The was canceled.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken)
{
WaitAll(tasks, Timeout.Infinite, cancellationToken);
}
///
/// Waits for all of the provided objects to complete execution.
///
///
/// true if all of the instances completed execution within the allotted time;
/// otherwise, false.
///
///
/// An array of instances on which to wait.
///
///
/// The number of milliseconds to wait, or (-1) to
/// wait indefinitely.
///
///
/// A to observe while waiting for the tasks to complete.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// At least one of the instances was canceled -or- an exception was thrown during
/// the execution of at least one of the instances.
///
///
/// is a negative number other than -1, which represents an
/// infinite time-out.
///
///
/// The was canceled.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
if (tasks == null)
{
throw new ArgumentNullException("tasks");
}
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException("millisecondsTimeout");
}
Contract.EndContractBlock();
cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations
//
// In this WaitAll() implementation we have 2 alternate code paths for a task to be handled:
// CODEPATH1: skip an already completed task, CODEPATH2: actually wait on tasks
// We make sure that the exception behavior of Task.Wait() is replicated the same for tasks handled in either of these codepaths
//
List exceptions = null;
List waitedOnTaskList = null;
List notificationTasks = null;
// If any of the waited-upon tasks end as Faulted or Canceled, set these to true.
var exceptionSeen = false;
var cancellationSeen = false;
var allCompleted = true;
// Collects incomplete tasks in "waitedOnTaskList"
for (var taskIndex = tasks.Length - 1; taskIndex >= 0; taskIndex--)
{
var task = tasks[taskIndex];
if (task == null)
{
throw new ArgumentException("The tasks array included at least one null element.");
}
var taskIsCompleted = task.IsCompleted;
if (!taskIsCompleted)
{
// try inlining the task only if we have an infinite timeout and an empty cancellation token
if (millisecondsTimeout != Timeout.Infinite || cancellationToken.CanBeCanceled)
{
// We either didn't attempt inline execution because we had a non-infinite timeout or we had a cancellable token.
// In all cases we need to do a full wait on the task (=> add its event into the list.)
AddToList(task, ref waitedOnTaskList, /*initSize:*/ tasks.Length);
}
else
{
// We are eligible for inlining. If it doesn't work, we'll do a full wait.
taskIsCompleted = task.TryStart(task.ExecutingTaskScheduler, true) && task.IsCompleted; // A successful TryRunInline doesn't guarantee completion
if (!taskIsCompleted)
{
AddToList(task, ref waitedOnTaskList, /*initSize:*/ tasks.Length);
}
}
}
if (taskIsCompleted)
{
if (task.IsFaulted)
{
exceptionSeen = true;
}
else
{
cancellationSeen |= task.IsCanceled;
}
if (task.IsWaitNotificationEnabled)
{
AddToList(task, ref notificationTasks, /*initSize:*/ 1);
}
}
}
if (waitedOnTaskList != null)
{
// Block waiting for the tasks to complete.
allCompleted = WaitAllBlockingCore(waitedOnTaskList, millisecondsTimeout, cancellationToken);
// If the wait didn't time out, ensure exceptions are propagated, and if a debugger is
// attached and one of these tasks requires it, that we notify the debugger of a wait completion.
if (allCompleted)
{
// Add any exceptions for this task to the collection, and if it's wait
// notification bit is set, store it to operate on at the end.
foreach (var task in waitedOnTaskList)
{
if (task.IsFaulted)
{
exceptionSeen = true;
}
else
{
cancellationSeen |= task.IsCanceled;
}
if (task.IsWaitNotificationEnabled)
{
AddToList(task, ref notificationTasks, /*initSize:*/ 1);
}
}
}
// We need to prevent the tasks array from being GC'ed until we come out of the wait.
// This is necessary so that the Parallel Debugger can traverse it during the long wait and
// deduce waiter/waitee relationships
GC.KeepAlive(tasks);
}
// Now that we're done and about to exit, if the wait completed and if we have
// any tasks with a notification bit set, signal the debugger if any requires it.
if (allCompleted && notificationTasks != null)
{
// Loop through each task tha that had its bit set, and notify the debugger
// about the first one that requires it. The debugger will reset the bit
// for any tasks we don't notify of as soon as we break, so we only need to notify
// for one.
foreach (var task in notificationTasks)
{
if (task.NotifyDebuggerOfWaitCompletionIfNecessary())
{
break;
}
}
}
// If one or more threw exceptions, aggregate and throw them.
if (allCompleted && (exceptionSeen || cancellationSeen))
{
// If the WaitAll was canceled and tasks were canceled but not faulted,
// prioritize throwing an OCE for canceling the WaitAll over throwing an
// AggregateException for all of the canceled Tasks. This helps
// to bring determinism to an otherwise non-determistic case of using
// the same token to cancel both the WaitAll and the Tasks.
if (!exceptionSeen)
{
cancellationToken.ThrowIfCancellationRequested();
}
// Now gather up and throw all of the exceptions.
foreach (var task in tasks)
{
AddExceptionsForCompletedTask(ref exceptions, task);
}
Contract.Assert(exceptions != null, "Should have seen at least one exception");
throw new AggregateException(exceptions);
}
return allCompleted;
}
/// Adds an element to the list, initializing the list if it's null.
/// Specifies the type of data stored in the list.
/// The item to add.
/// The list.
/// The size to which to initialize the list if the list is null.
private static void AddToList(T item, ref List list, int initSize)
{
if (list == null)
{
list = new List(initSize);
}
list.Add(item);
}
/// Performs a blocking WaitAll on the vetted list of tasks.
/// The tasks, which have already been checked and filtered for completion.
/// The timeout.
/// The cancellation token.
/// true if all of the tasks completed; otherwise, false.
private static bool WaitAllBlockingCore(List tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
if (tasks == null)
{
Contract.Assert(false, "Expected a non-null list of tasks");
throw new ArgumentNullException("tasks");
}
Contract.Assert(tasks.Count > 0, "Expected at least one task");
bool waitCompleted;
ManualResetEventSlim mres = null;
WhenAllCore core = null;
try
{
mres = new ManualResetEventSlim(false);
core = new WhenAllCore(tasks, mres.Set);
if (core.IsDone)
{
waitCompleted = true;
}
else
{
waitCompleted = mres.Wait(millisecondsTimeout, cancellationToken);
}
}
finally
{
if (core != null)
{
core.Dispose();
waitCompleted = core.IsDone;
}
if (mres != null)
{
mres.Dispose();
}
}
return waitCompleted;
}
internal static void FastWaitAll(Task[] tasks)
{
Contract.Requires(tasks != null);
List exceptions = null;
// Collects incomplete tasks in "waitedOnTaskList" and their cooperative events in "cooperativeEventList"
for (var taskIndex = tasks.Length - 1; taskIndex >= 0; taskIndex--)
{
if (!tasks[taskIndex].IsCompleted)
{
// Just attempting to inline here... result doesn't matter.
// We'll do a second pass to do actual wait on each task, and to aggregate their exceptions.
// If the task is inlined here, it will register as IsCompleted in the second pass
// and will just give us the exception.
tasks[taskIndex].TryStart(tasks[taskIndex].ExecutingTaskScheduler, true);
}
}
// Wait on the tasks.
for (var taskIndex = tasks.Length - 1; taskIndex >= 0; taskIndex--)
{
var task = tasks[taskIndex];
task.Wait();
AddExceptionsForCompletedTask(ref exceptions, task);
// Note that unlike other wait code paths, we do not check
// task.NotifyDebuggerOfWaitCompletionIfNecessary() here, because this method is currently
// only used from contexts where the tasks couldn't have that bit set, namely
// Parallel.Invoke. If that ever changes, such checks should be added here.
}
// If one or more threw exceptions, aggregate them.
if (exceptions != null)
{
throw new AggregateException(exceptions);
}
}
internal static void AddExceptionsForCompletedTask(ref List exceptions, Task t)
{
var ex = t.GetExceptions(true);
if (ex != null)
{
// make sure the task's exception observed status is set appropriately
// it's possible that WaitAll was called by the parent of an attached child,
// this will make sure it won't throw again in the implicit wait
t.UpdateExceptionObservedStatus();
if (exceptions == null)
{
exceptions = new List(ex.InnerExceptions.Count);
}
exceptions.AddRange(ex.InnerExceptions);
}
}
///
/// Waits for any of the provided objects to complete execution.
///
///
/// An array of instances on which to wait.
///
/// The index of the completed task in the array argument.
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static int WaitAny(params Task[] tasks)
{
var waitResult = WaitAny(tasks, Timeout.Infinite);
Contract.Assert(tasks.Length == 0 || waitResult != -1, "expected wait to succeed");
return waitResult;
}
///
/// Waits for any of the provided objects to complete execution.
///
///
/// An array of instances on which to wait.
///
///
/// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely.
///
///
/// The index of the completed task in the array argument, or -1 if the
/// timeout occurred.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// is a negative number other than -1 milliseconds, which represents an
/// infinite time-out -or- timeout is greater than
/// .
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static int WaitAny(Task[] tasks, TimeSpan timeout)
{
var totalMilliseconds = (long)timeout.TotalMilliseconds;
if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
{
throw new ArgumentOutOfRangeException("timeout");
}
return WaitAny(tasks, (int)totalMilliseconds);
}
///
/// Waits for any of the provided objects to complete execution.
///
///
/// An array of instances on which to wait.
///
///
/// A to observe while waiting for a task to complete.
///
///
/// The index of the completed task in the array argument.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// The was canceled.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static int WaitAny(Task[] tasks, CancellationToken cancellationToken)
{
return WaitAny(tasks, Timeout.Infinite, cancellationToken);
}
///
/// Waits for any of the provided objects to complete execution.
///
///
/// An array of instances on which to wait.
///
///
/// The number of milliseconds to wait, or (-1) to
/// wait indefinitely.
///
///
/// The index of the completed task in the array argument, or -1 if the
/// timeout occurred.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// is a negative number other than -1, which represents an
/// infinite time-out.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static int WaitAny(Task[] tasks, int millisecondsTimeout)
{
return WaitAny(tasks, millisecondsTimeout, default(CancellationToken));
}
///
/// Waits for any of the provided objects to complete execution.
///
///
/// An array of instances on which to wait.
///
///
/// The number of milliseconds to wait, or (-1) to
/// wait indefinitely.
///
///
/// A to observe while waiting for a task to complete.
///
///
/// The index of the completed task in the array argument, or -1 if the
/// timeout occurred.
///
///
/// The argument is null.
///
///
/// The argument contains a null element.
///
///
/// is a negative number other than -1, which represents an
/// infinite time-out.
///
///
/// The was canceled.
///
[MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger
public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken)
{
if (tasks == null)
{
throw new ArgumentNullException("tasks");
}
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException("millisecondsTimeout");
}
Contract.EndContractBlock();
cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations
var signaledTaskIndex = -1;
// Make a pass through the loop to check for any tasks that may have
// already been completed, and to verify that no tasks are null.
for (var taskIndex = 0; taskIndex < tasks.Length; taskIndex++)
{
var task = tasks[taskIndex];
if (task == null)
{
throw new ArgumentException("The tasks array included at least one null element.", "tasks");
}
if (signaledTaskIndex == -1 && task.IsCompleted)
{
// We found our first completed task. Store it, but we can't just return here,
// as we still need to validate the whole array for nulls.
signaledTaskIndex = taskIndex;
}
}
if (signaledTaskIndex == -1 && tasks.Length != 0)
{
PrivateWaitAny(tasks, millisecondsTimeout, cancellationToken, ref signaledTaskIndex);
}
// We need to prevent the tasks array from being GC'ed until we come out of the wait.
// This is necessary so that the Parallel Debugger can traverse it during the long wait
// and deduce waiter/waitee relationships
GC.KeepAlive(tasks);
// Return the index
return signaledTaskIndex;
}
private static Task PrivateWaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken, ref int signaledTaskIndex)
{
var firstCompleted = PrivateWhenAny(tasks, ref signaledTaskIndex);
if (signaledTaskIndex == -1)
{
var waitCompleted = firstCompleted.Wait(millisecondsTimeout, cancellationToken);
if (waitCompleted)
{
signaledTaskIndex = Array.IndexOf(tasks, firstCompleted.Result);
}
}
return firstCompleted;
}
private static Task PrivateWhenAny(IList tasks, ref int signaledTaskIndex)
{
var firstCompleted = new CompleteOnInvokePromise(tasks);
for (var taskIndex = 0; taskIndex < tasks.Count; taskIndex++)
{
var task = tasks[taskIndex];
// If a task has already completed, complete the promise.
if (task.IsCompleted)
{
firstCompleted.Invoke(task);
signaledTaskIndex = taskIndex;
break;
}
// Otherwise, add the completion action and keep going.
task.AddTaskContinuation(firstCompleted, false);
}
//--
return firstCompleted;
}
}
}
#endif